├── .ci
├── build-demo.sh
└── template.json
├── .dockerignore
├── .github
└── workflows
│ ├── ci.yml
│ ├── demo-pages.yml
│ └── docker-publish.yml
├── .gitignore
├── Dockerfile
├── LICENSE
├── Makefile
├── README.md
├── bin
└── satis-gitlab
├── composer.json
├── composer.lock
├── docs
├── docker.md
└── options.md
├── phpunit.xml.dist
├── src
└── MBO
│ └── SatisGitlab
│ ├── Command
│ └── GitlabToConfigCommand.php
│ ├── GitFilter
│ └── GitlabNamespaceFilter.php
│ ├── Resources
│ └── default-template.json
│ └── Satis
│ └── ConfigBuilder.php
└── tests
├── Command
├── GitlabToConfigCommandTest.php
└── expected-with-filter.json
├── Satis
├── ConfigBuilderTest.php
└── expected-repositories.json
└── TestCase.php
/.ci/build-demo.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
4 | PROJECT_DIR=$(dirname "$SCRIPT_DIR")
5 |
6 | if [ -z "$SATIS_GITHUB_TOKEN" ];
7 | then
8 | echo "SATIS_GITHUB_TOKEN required"
9 | exit
10 | fi
11 |
12 | cd "$PROJECT_DIR"
13 |
14 | # configure github authentication for composer
15 | if [ "$GITHUB_ACTIONS" = "true" ]; then
16 | composer config -g github-oauth.github.com $SATIS_GITHUB_TOKEN
17 | fi
18 |
19 | # generate the satis config file (satis.json)
20 | bin/satis-gitlab gitlab-to-config \
21 | --template ".ci/template.json" \
22 | https://github.com $SATIS_GITHUB_TOKEN \
23 | --users=mborne \
24 | --ignore="(^mborne\\/php-helloworld)" \
25 | --output satis.json
26 |
27 | # build public directory
28 | bin/satis-gitlab build --no-interaction --skip-errors satis.json public
29 |
--------------------------------------------------------------------------------
/.ci/template.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "mborne/demo-satis-gitlab",
3 | "homepage": "https://mborne.github.io/satis-gitlab/",
4 | "repositories": [
5 | {
6 | "type": "composer",
7 | "url": "https://packagist.org"
8 | }
9 | ],
10 | "require": [],
11 | "require-dependencies": false,
12 | "require-dev-dependencies": false
13 | }
--------------------------------------------------------------------------------
/.dockerignore:
--------------------------------------------------------------------------------
1 | /vendor/
2 | /output/
3 | /.git/
4 | /satis.json
5 |
6 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 |
3 | on:
4 | push:
5 | branches: [ "master" ]
6 | pull_request:
7 | branches: [ "master" ]
8 |
9 | permissions:
10 | contents: read
11 |
12 | jobs:
13 | build:
14 |
15 | strategy:
16 | matrix:
17 | php-version: [8.2,8.3]
18 |
19 | runs-on: ubuntu-latest
20 |
21 | steps:
22 | - uses: actions/checkout@v3
23 |
24 | - name: "Setup PHP ${{ matrix.php-version }}"
25 | uses: shivammathur/setup-php@v2
26 | with:
27 | php-version: ${{ matrix.php-version }}
28 | coverage: xdebug2
29 | #coverage: xdebug
30 | tools: php-cs-fixer, phpunit
31 |
32 | - name: Validate composer.json
33 | run: composer validate --strict
34 |
35 | - name: Cache Composer packages
36 | id: composer-cache
37 | uses: actions/cache@v3
38 | with:
39 | path: vendor
40 | key: ${{ runner.os }}-php-${{ hashFiles('**/composer.lock') }}
41 | restore-keys: |
42 | ${{ runner.os }}-php-
43 |
44 | - name: Install dependencies
45 | run: composer update --prefer-dist --no-progress
46 |
47 | - name: Run tests
48 | run: make test
49 | env:
50 | SATIS_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
51 | SATIS_GITLAB_TOKEN: ${{ secrets.GITLAB_TOKEN }}
52 |
53 | - name: Upload coverage results to coveralls.io
54 | if: github.ref == 'refs/heads/master' && matrix.php-version == '8.1'
55 | run: |
56 | vendor/bin/php-coveralls --coverage_clover=output/clover.xml --json_path=output/coveralls.json -v
57 | env:
58 | COVERALLS_REPO_TOKEN: ${{ secrets.GITHUB_TOKEN }}
59 |
--------------------------------------------------------------------------------
/.github/workflows/demo-pages.yml:
--------------------------------------------------------------------------------
1 | name: Build and publish demo on GitHub pages
2 |
3 | on:
4 | push:
5 | branches: [ "master" ]
6 |
7 | jobs:
8 | build:
9 | runs-on: ubuntu-latest
10 |
11 | steps:
12 | - uses: actions/checkout@v3
13 |
14 | - name: "Setup PHP 8.3"
15 | uses: shivammathur/setup-php@v2
16 | with:
17 | php-version: 8.3
18 |
19 | - name: Validate composer.json
20 | run: composer validate --strict
21 |
22 | - name: Cache Composer packages
23 | id: composer-cache
24 | uses: actions/cache@v3
25 | with:
26 | path: vendor
27 | key: ${{ runner.os }}-php-${{ hashFiles('**/composer.lock') }}
28 | restore-keys: |
29 | ${{ runner.os }}-php-
30 |
31 | - name: Install dependencies
32 | run: composer install --prefer-dist --no-progress
33 |
34 | - name: Build demo
35 | run: bash .ci/build-demo.sh
36 | env:
37 | SATIS_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
38 |
39 | - name: Upload static files from public as artifact
40 | id: deployment
41 | uses: actions/upload-pages-artifact@v3
42 | with:
43 | path: public/
44 |
45 | # Deploy pages
46 | deploy:
47 | needs: build
48 |
49 | # Grant GITHUB_TOKEN the permissions required to make a Pages deployment
50 | permissions:
51 | pages: write # to deploy to Pages
52 | id-token: write # to verify the deployment originates from an appropriate source
53 |
54 | # Deploy to the github-pages environment
55 | environment:
56 | name: github-pages
57 | url: ${{ steps.deployment.outputs.page_url }}
58 |
59 | # Specify runner + deployment step
60 | runs-on: ubuntu-latest
61 | steps:
62 | - name: Deploy to GitHub Pages
63 | id: deployment
64 | uses: actions/deploy-pages@v4
65 |
--------------------------------------------------------------------------------
/.github/workflows/docker-publish.yml:
--------------------------------------------------------------------------------
1 | name: Docker
2 |
3 | on:
4 | push:
5 | branches: [ "master" ]
6 | tags: [ 'v*.*.*' ]
7 | pull_request:
8 | branches: [ "master" ]
9 |
10 | env:
11 | REGISTRY: ghcr.io
12 | IMAGE_NAME: ${{ github.repository }}
13 |
14 | jobs:
15 | build:
16 |
17 | runs-on: ubuntu-latest
18 | permissions:
19 | contents: read
20 | packages: write
21 | # This is used to complete the identity challenge
22 | # with sigstore/fulcio when running outside of PRs.
23 | id-token: write
24 |
25 | steps:
26 | - name: Checkout repository
27 | uses: actions/checkout@v4
28 |
29 | # Set up BuildKit Docker container builder to be able to build
30 | # multi-platform images and export cache
31 | # https://github.com/docker/setup-buildx-action
32 | - name: Set up Docker Buildx
33 | uses: docker/setup-buildx-action@v3
34 |
35 | # Login against a Docker registry except on PR
36 | # https://github.com/docker/login-action
37 | - name: Log into registry ${{ env.REGISTRY }}
38 | if: github.event_name != 'pull_request'
39 | uses: docker/login-action@v3
40 | with:
41 | registry: ${{ env.REGISTRY }}
42 | username: ${{ github.actor }}
43 | password: ${{ secrets.GITHUB_TOKEN }}
44 |
45 | # Extract metadata (tags, labels) for Docker
46 | # https://github.com/docker/metadata-action
47 | - name: Extract Docker metadata
48 | id: meta
49 | uses: docker/metadata-action@v5
50 | with:
51 | images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
52 |
53 | # Build and push Docker image with Buildx (don't push on PR)
54 | # https://github.com/docker/build-push-action
55 | - name: Build and push Docker image
56 | id: build-and-push
57 | uses: docker/build-push-action@v5
58 | with:
59 | context: .
60 | platforms: linux/arm64/v8,linux/amd64
61 | push: ${{ github.event_name != 'pull_request' }}
62 | tags: ${{ steps.meta.outputs.tags }}
63 | labels: ${{ steps.meta.outputs.labels }}
64 | cache-from: type=gha
65 | cache-to: type=gha,mode=max
66 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /nbproject/
2 | /vendor/
3 | /satis.json
4 | /web/
5 | /output/
6 | /public/
7 |
8 | /composer.phar
9 | /.phpunit.result.cache
10 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM composer:latest
2 |
3 | ARG UID=1000
4 | ARG GID=1000
5 | RUN addgroup --gid "$GID" satis \
6 | && adduser \
7 | --disabled-password \
8 | --gecos "" \
9 | --home "/home/satis-gitlab" \
10 | --ingroup "satis" \
11 | --uid "$UID" \
12 | satis
13 |
14 | RUN mkdir -p /opt/satis-gitlab
15 | WORKDIR /opt/satis-gitlab
16 | COPY composer.json .
17 | COPY composer.lock .
18 | RUN composer install
19 |
20 | WORKDIR /opt/satis-gitlab
21 | COPY src/ src
22 | COPY bin/ bin
23 |
24 | RUN mkdir -p /opt/satis-gitlab/config \
25 | && chown -R satis:satis /opt/satis-gitlab/config
26 | VOLUME /opt/satis-gitlab/config
27 |
28 | RUN mkdir -p /opt/satis-gitlab/public \
29 | && chown -R satis:satis /opt/satis-gitlab/public
30 | VOLUME /opt/satis-gitlab/public
31 |
32 | USER satis
33 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) mborne
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy
4 | of this software and associated documentation files (the "Software"), to deal
5 | in the Software without restriction, including without limitation the rights
6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | copies of the Software, and to permit persons to whom the Software is furnished
8 | to do so, subject to the following conditions:
9 |
10 | The above copyright notice and this permission notice shall be included in all
11 | copies or substantial portions of the Software.
12 |
13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19 | THE SOFTWARE.
20 |
21 |
22 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | .PHONY: test install
2 |
3 | test: install
4 | XDEBUG_MODE=coverage vendor/bin/phpunit -c phpunit.xml.dist \
5 | --log-junit output/junit-report.xml \
6 | --coverage-clover output/clover.xml \
7 | --coverage-html output/coverage
8 |
9 | install: composer.phar
10 | php composer.phar install
11 |
12 | composer.phar:
13 | curl -s https://getcomposer.org/installer | php
14 | chmod +x composer.phar
15 |
16 |
17 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # mborne/satis-gitlab
2 |
3 | [](https://github.com/mborne/satis-gitlab/actions/workflows/ci.yml) [](https://coveralls.io/github/mborne/satis-gitlab?branch=master)
4 |
5 | [PHP composer/satis](https://github.com/composer/satis) extended with the ability to generate SATIS configuration according to CVS projects containing a `composer.json` file.
6 |
7 | It also provides a way to mirror PHP dependencies to allow offline builds.
8 |
9 | ## Requirements
10 |
11 | * [PHP >=8.2](https://www.php.net/supported-versions.php)
12 | * GitLab API v4 / GitHub API / Gogs API / Gitea API
13 |
14 | ## Usage
15 |
16 | ### 1) Create SATIS project
17 |
18 | ```bash
19 | git clone https://github.com/mborne/satis-gitlab
20 | cd satis-gitlab
21 | # PHP 8.1
22 | composer install
23 | # PHP 7.4 (downgrading versions refered in composer.lock is required)
24 | composer update
25 | ```
26 |
27 |
28 | ### 2) Generate SATIS configuration
29 |
30 | ```bash
31 | # add --archive if you want to mirror tar archives
32 | bin/satis-gitlab gitlab-to-config \
33 | --homepage https://satis.example.org \
34 | --output satis.json \
35 | https://gitlab.example.org [GitlabToken]
36 | ```
37 |
38 | ### 3) Use SATIS as usual
39 |
40 | ```bash
41 | bin/satis-gitlab build satis.json web
42 | ```
43 |
44 | ### 4) Configure a static file server for the web directory
45 |
46 | Use you're favorite tool to expose `web` directory as `https://satis.example.org`.
47 |
48 | **satis.json should not be exposed, it contains the GitlabToken by default (see `--no-token`)**
49 |
50 | ### 5) Configure clients
51 |
52 | #### Option 1 : Configure projects to use SATIS
53 |
54 | SATIS web page suggests to add the following configuration to composer.json in all your projects :
55 |
56 | ```json
57 | {
58 | "repositories": [{
59 | "type": "composer",
60 | "url": "https://satis.example.org"
61 | }]
62 | }
63 | ```
64 |
65 | #### Option 2 : Configure composer to use SATIS
66 |
67 | Alternatively, composer can be configured globally to use SATIS :
68 |
69 | ```bash
70 | composer config --global repo.satis.example.org composer https://satis.example.org
71 | ```
72 |
73 | (it makes a weaker link between your projects and your SATIS instance(s))
74 |
75 |
76 | ## Advanced usage
77 |
78 | ### Filter by organization/groups and users
79 |
80 | If you rely on gitlab.com, you will probably need to find projects according to groups and users :
81 |
82 | ```bash
83 | bin/satis-gitlab gitlab-to-config https://gitlab.com $SATIS_GITLAB_TOKEN -vv --users=mborne --orgs=drutopia
84 | ```
85 |
86 | ## Build configuration according to github repositories
87 |
88 | github supports allows to perform :
89 |
90 | ```bash
91 | bin/satis-gitlab gitlab-to-config https://github.com $SATIS_GITHUB_TOKEN --orgs=symfony --users=mborne
92 | bin/satis-gitlab build --skip-errors satis.json web
93 | ```
94 |
95 | (Note that SATIS_GITHUB_TOKEN is required to avoid rate request limitation)
96 |
97 |
98 | ### Mirror dependencies
99 |
100 | Note that `--archive` option allows to download `tar` archives for each tag and each branch in `web/dist` for :
101 |
102 | * The gitlab projects
103 | * The dependencies of the gitlab projects
104 |
105 |
106 | ### Expose only public repositories
107 |
108 | Note that `GitlabToken` is optional so that you can generate a SATIS instance only for you're public repositories.
109 |
110 |
111 | ### Disable GitlabToken saving
112 |
113 | Note that `gitlab-to-config` saves the `GitlabToken` to `satis.json` configuration file (so far you expose only the `web` directory, it is not a problem).
114 |
115 | You may disable this option using `--no-token` option and use the following composer command to configure `$COMPOSER_HOME/auth.json` file :
116 |
117 | `composer config -g gitlab-token.satis.example.org GitlabToken`
118 |
119 |
120 | ### Deep customization
121 |
122 | Some command line options provide a basic customization options. You may also use `--template my-satis-template.json` to replace the default template :
123 |
124 | [default-template.json](src/MBO/SatisGitlab/Resources/default-template.json)
125 |
126 | ## Usage with docker
127 |
128 | See [docs/docker.md](docs/docker.md).
129 |
130 | ## Testing
131 |
132 | ```bash
133 | export SATIS_GITLAB_TOKEN=AnyGitlabToken
134 | export SATIS_GITHUB_TOKEN=AnyGithubToken
135 |
136 | make test
137 | ```
138 |
139 | Note that an HTML coverage report is generated to `output/coverage/index.html`
140 |
141 |
142 | ## Demo
143 |
144 | https://mborne.github.io/satis-gitlab/ is built using github actions. See :
145 |
146 | * [.github/workflows/demo-pages.yml](.github/workflows/demo-pages.yml)
147 | * [.ci/build-demo.sh](.ci/build-demo.sh)
148 |
149 | ## License
150 |
151 | [MIT](LICENSE).
152 |
153 |
154 |
--------------------------------------------------------------------------------
/bin/satis-gitlab:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env php
2 |
8 | *
9 | * For the full copyright and license information, please view
10 | * the LICENSE file that was distributed with this source code.
11 | */
12 |
13 | function includeIfExists($file)
14 | {
15 | if (file_exists($file)) {
16 | return include $file;
17 | }
18 | }
19 |
20 | if ((!$loader = includeIfExists(__DIR__.'/../vendor/autoload.php')) && (!$loader = includeIfExists(__DIR__.'/../../../autoload.php'))) {
21 | print('You must set up the project dependencies using Composer before you can use Satis.');
22 | exit(1);
23 | }
24 |
25 | /*
26 | * create extended satis application
27 | */
28 | $application = new Composer\Satis\Console\Application();
29 | $application->add(new \MBO\SatisGitlab\Command\GitlabToConfigCommand());
30 | $application->run();
31 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "mborne/satis-gitlab",
3 | "type": "project",
4 | "description": "composer/satis extended with the ability to generate SATIS configuration",
5 | "authors": [
6 | {
7 | "name": "Mickaël BORNE",
8 | "email": "mborne@users.noreply.github.com"
9 | }
10 | ],
11 | "license": "MIT",
12 | "autoload": {
13 | "psr-4": {
14 | "MBO\\SatisGitlab\\": "src/MBO/SatisGitlab"
15 | }
16 | },
17 | "autoload-dev": {
18 | "psr-4": {
19 | "MBO\\SatisGitlab\\Tests\\": "tests"
20 | }
21 | },
22 | "bin": [
23 | "bin/satis-gitlab"
24 | ],
25 | "require": {
26 | "mborne/remote-git": "^0.8",
27 | "symfony/console": "^5.4|^6.0|^7.0",
28 | "composer/satis": "dev-main"
29 | },
30 | "require-dev": {
31 | "phpunit/phpunit": "^9",
32 | "php-coveralls/php-coveralls": "^2.5"
33 | },
34 | "config": {
35 | "allow-plugins": {
36 | "composer/satis": true
37 | }
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/docs/docker.md:
--------------------------------------------------------------------------------
1 | # satis-gitlab - Usage with docker
2 |
3 | ## Motivation
4 |
5 | Provide a docker image for satis-gitlab to :
6 |
7 | * Create a static site with pages using GitLab-CI or GitHub actions
8 | * Ease the built of a custom image with a custom update loop
9 |
10 | ## Build image
11 |
12 | > Alternative : use [ghcr.io/mborne/satis-gitlab:master](https://github.com/mborne/satis-gitlab/pkgs/container/satis-gitlab) instead of satis-gitlab bellow.
13 |
14 | ```bash
15 | docker build -t satis-gitlab .
16 | ```
17 |
18 | ## Create static site content
19 |
20 | ```bash
21 | # create satis-gitlab container
22 | docker run \
23 | -v satis-data:/opt/satis-gitlab/public \
24 | -v satis-config:/opt/satis-gitlab/config \
25 | --env-file=../satis-gitlab.env \
26 | --rm -ti satis-gitlab /bin/bash
27 |
28 | # generate config/satis.json
29 | bin/satis-gitlab gitlab-to-config \
30 | --homepage https://satis.dev.localhost \
31 | --output config/satis.json https://github.com \
32 | --users=mborne $SATIS_GITHUB_TOKEN
33 |
34 | # generate public from config/satis.json with satis
35 | git config --global github.accesstoken $SATIS_GITHUB_TOKEN
36 | bin/satis-gitlab build config/satis.json public -v
37 | ```
38 |
39 | ## Serve static site content
40 |
41 | ```bash
42 | # see http://localhost:8888
43 | docker run --rm -ti -v satis-data:/usr/share/nginx/html -p 8888:8080 nginxinc/nginx-unprivileged:1.26
44 | ```
45 |
46 |
47 |
--------------------------------------------------------------------------------
/docs/options.md:
--------------------------------------------------------------------------------
1 | # Notes about some options
2 |
3 | ## unsafe-ssl
4 |
5 | Using `--unsafe-ssl` produce the following output for repositories :
6 |
7 | ```json
8 | {
9 | "options": {
10 | "ssl": {
11 | "allow_self_signed": true,
12 | "verify_peer": false,
13 | "verify_peer_name": false
14 | }
15 | },
16 | "type": "vcs",
17 | "url": "https://gitlab.com/mborne/sample-composer.git"
18 | }
19 | ```
20 |
--------------------------------------------------------------------------------
/phpunit.xml.dist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | ./src
6 |
7 |
8 |
9 |
10 | ./tests
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/src/MBO/SatisGitlab/Command/GitlabToConfigCommand.php:
--------------------------------------------------------------------------------
1 | setName('gitlab-to-config')
48 |
49 | // the short description shown while running "php bin/console list"
50 | ->setDescription('generate satis configuration scanning gitlab repositories')
51 | ->setHelp('look for composer.json in default gitlab branche, extract project name and register them in SATIS configuration')
52 |
53 | /*
54 | * Git client options
55 | */
56 | ->addArgument('gitlab-url', InputArgument::REQUIRED)
57 | ->addArgument('gitlab-token')
58 | ->addOption('unsafe-ssl', null, InputOption::VALUE_NONE, 'allows to ignore SSL problems')
59 |
60 | /*
61 | * Project listing options (hosted git api level)
62 | */
63 | ->addOption('orgs', 'o', InputOption::VALUE_REQUIRED, 'Find projects according to given organization names')
64 | ->addOption('users', 'u', InputOption::VALUE_REQUIRED, 'Find projects according to given user names')
65 | ->addOption('projectFilter', 'p', InputOption::VALUE_OPTIONAL, 'filter for projects (deprecated : see organization and users)', null)
66 |
67 | /*
68 | * Project filters
69 | */
70 | ->addOption('ignore', 'i', InputOption::VALUE_REQUIRED, 'ignore project according to a regexp, for ex : "(^phpstorm|^typo3\/library)"', null)
71 | ->addOption('include-if-has-file',null,InputOption::VALUE_REQUIRED, 'include in satis config if project contains a given file, for ex : ".satisinclude"', null)
72 | ->addOption('project-type',null,InputOption::VALUE_REQUIRED, 'include in satis config if project is of a specified type, for ex : "library"', null)
73 | ->addOption('gitlab-namespace',null,InputOption::VALUE_REQUIRED, 'include in satis config if gitlab project namespace is in the list, for ex : "2,Diaspora" (deprecated : see organization and users)', null)
74 | /*
75 | * satis config generation options
76 | */
77 | // deep customization : template file extended with default configuration
78 | ->addOption('template', null, InputOption::VALUE_REQUIRED, 'template satis.json extended with gitlab repositories', $templatePath)
79 |
80 | // simple customization on default-template.json
81 | ->addOption('name', null, InputOption::VALUE_REQUIRED, 'satis repository name')
82 | ->addOption('homepage', null, InputOption::VALUE_REQUIRED, 'satis homepage')
83 | ->addOption('archive', null, InputOption::VALUE_NONE, 'enable archive mirroring')
84 | ->addOption('no-token', null, InputOption::VALUE_NONE, 'disable token writing in output configuration')
85 |
86 | /*
87 | * output options
88 | */
89 | ->addOption('output', 'O', InputOption::VALUE_REQUIRED, 'output config file', 'satis.json')
90 | ;
91 | }
92 |
93 | /**
94 | * @{inheritDoc}
95 | */
96 | protected function execute(InputInterface $input, OutputInterface $output): int
97 | {
98 | $logger = $this->createLogger($output);
99 |
100 | /*
101 | * Create git client according to parameters
102 | */
103 | $clientOptions = new ClientOptions();
104 | $clientOptions->setUrl($input->getArgument('gitlab-url'));
105 | $clientOptions->setToken($input->getArgument('gitlab-token'));
106 |
107 | if ( $input->getOption('unsafe-ssl') ){
108 | $clientOptions->setUnsafeSsl(true);
109 | }
110 |
111 | $client = ClientFactory::createClient(
112 | $clientOptions,
113 | $logger
114 | );
115 |
116 | $outputFile = $input->getOption('output');
117 |
118 | /*
119 | * Create repository listing filter (git level)
120 | */
121 | $findOptions = new FindOptions();
122 | /* orgs option */
123 | $orgs = $input->getOption('orgs');
124 | if ( ! empty($orgs) ){
125 | $findOptions->setOrganizations(explode(',',$orgs));
126 | }
127 | /* users option */
128 | $users = $input->getOption('users');
129 | if ( ! empty($users) ){
130 | $findOptions->setUsers(explode(',',$users));
131 | }
132 |
133 | /* projectFilter option */
134 | $projectFilter = $input->getOption('projectFilter');
135 | if ( ! empty($projectFilter) ) {
136 | $logger->info(sprintf("Project filter : %s...", $projectFilter));
137 | $findOptions->setSearch($projectFilter);
138 | }
139 |
140 | /*
141 | * Create project filters according to input arguments
142 | */
143 | $filterCollection = new FilterCollection($logger);
144 | $findOptions->setFilter($filterCollection);
145 |
146 | /*
147 | * Filter according to "composer.json" file
148 | */
149 | $composerFilter = new ComposerProjectFilter($client,$logger);
150 | /* project-type option */
151 | if ( ! empty($input->getOption('project-type')) ){
152 | $composerFilter->setProjectType($input->getOption('project-type'));
153 | }
154 | $filterCollection->addFilter($composerFilter);
155 |
156 |
157 | /* include-if-has-file option (TODO : project listing level) */
158 | if ( ! empty($input->getOption('include-if-has-file')) ){
159 | $filterCollection->addFilter(new RequiredFileFilter(
160 | $client,
161 | $input->getOption('include-if-has-file'),
162 | $logger
163 | ));
164 | }
165 |
166 | /*
167 | * Filter according to git project properties
168 | */
169 |
170 | /* ignore option */
171 | if ( ! empty($input->getOption('ignore')) ){
172 | $filterCollection->addFilter(new IgnoreRegexpFilter(
173 | $input->getOption('ignore')
174 | ));
175 | }
176 |
177 | /* gitlab-namespace option */
178 | if ( ! empty($input->getOption('gitlab-namespace')) ){
179 | $filterCollection->addFilter(new GitlabNamespaceFilter(
180 | $input->getOption('gitlab-namespace')
181 | ));
182 | }
183 |
184 | /*
185 | * Create configuration builder
186 | */
187 | $templatePath = $input->getOption('template');
188 | $output->writeln(sprintf("Loading template %s...", $templatePath));
189 | $configBuilder = new ConfigBuilder($templatePath);
190 |
191 | /*
192 | * customize according to command line options
193 | */
194 | $name = $input->getOption('name');
195 | if ( ! empty($name) ){
196 | $configBuilder->setName($name);
197 | }
198 |
199 | $homepage = $input->getOption('homepage');
200 | if ( ! empty($homepage) ){
201 | $configBuilder->setHomepage($homepage);
202 | }
203 |
204 | // mirroring
205 | if ( $input->getOption('archive') ){
206 | $configBuilder->enableArchive();
207 | }
208 |
209 | /*
210 | * Register gitlab domain to enable composer gitlab-* authentications
211 | */
212 | $gitlabDomain = parse_url($clientOptions->getUrl(), PHP_URL_HOST);
213 | $configBuilder->addGitlabDomain($gitlabDomain);
214 |
215 | if ( ! $input->getOption('no-token') && $clientOptions->hasToken() ){
216 | $configBuilder->addGitlabToken(
217 | $gitlabDomain,
218 | $clientOptions->getToken(),
219 | $clientOptions->isUnsafeSsl()
220 | );
221 | }
222 |
223 | /*
224 | * SCAN gitlab projects to find composer.json file in default branch
225 | */
226 | $logger->info(sprintf(
227 | "Listing gitlab repositories from %s...",
228 | $clientOptions->getUrl()
229 | ));
230 |
231 | /*
232 | * Find projects
233 | */
234 | $projects = $client->find($findOptions);
235 |
236 | /* Generate SATIS configuration */
237 | $projectCount = 0;
238 | foreach ($projects as $project) {
239 | $projectUrl = $project->getHttpUrl();
240 |
241 | try {
242 | /* look for composer.json in default branch */
243 | $json = $client->getRawFile(
244 | $project,
245 | 'composer.json',
246 | $project->getDefaultBranch()
247 | );
248 |
249 | /* retrieve project name from composer.json content */
250 | $composer = json_decode($json, true);
251 | $projectName = isset($composer['name']) ? $composer['name'] : null;
252 | if (is_null($projectName)) {
253 | $logger->error($this->createProjectMessage(
254 | $project,
255 | "name not defined in composer.json"
256 | ));
257 | continue;
258 | }
259 |
260 | /* add project to satis config */
261 | $projectCount++;
262 | $logger->info($this->createProjectMessage(
263 | $project,
264 | "$projectName:*"
265 | ));
266 | $configBuilder->addRepository(
267 | $projectName,
268 | $projectUrl,
269 | $clientOptions->isUnsafeSsl()
270 | );
271 | } catch (\Exception $e) {
272 | $logger->debug($e->getMessage());
273 | $logger->warning($this->createProjectMessage(
274 | $project,
275 | 'composer.json not found'
276 | ));
277 | }
278 | }
279 |
280 | /* notify number of project found */
281 | if ( $projectCount == 0 ){
282 | $logger->error("No project found!");
283 | }else{
284 | $logger->info(sprintf(
285 | "Number of project found : %s",
286 | $projectCount
287 | ));
288 | }
289 |
290 | /*
291 | * Write resulting config
292 | */
293 | $satis = $configBuilder->getConfig();
294 | $logger->info("Generate satis configuration file : $outputFile");
295 | $result = json_encode($satis, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
296 | file_put_contents($outputFile, $result);
297 |
298 | return Command::SUCCESS;
299 | }
300 |
301 |
302 | /**
303 | * Create message for a given project
304 | */
305 | protected function createProjectMessage(
306 | ProjectInterface $project,
307 | $message
308 | ){
309 | return sprintf(
310 | '%s (branch %s) : %s',
311 | $project->getName(),
312 | $project->getDefaultBranch(),
313 | $message
314 | );
315 | }
316 |
317 | /**
318 | * Create console logger
319 | * @param OutputInterface $output
320 | * @return ConsoleLogger
321 | */
322 | protected function createLogger(OutputInterface $output){
323 | $verbosityLevelMap = array(
324 | LogLevel::NOTICE => OutputInterface::VERBOSITY_NORMAL,
325 | LogLevel::INFO => OutputInterface::VERBOSITY_NORMAL,
326 | );
327 | return new ConsoleLogger($output,$verbosityLevelMap);
328 | }
329 |
330 | }
331 |
--------------------------------------------------------------------------------
/src/MBO/SatisGitlab/GitFilter/GitlabNamespaceFilter.php:
--------------------------------------------------------------------------------
1 | groups = explode(',',strtolower($groups));
43 | }
44 |
45 | /**
46 | * {@inheritDoc}
47 | */
48 | public function getDescription(): string
49 | {
50 | return "gitlab namespace should be one of [".implode(', ',$this->groups)."]";
51 | }
52 |
53 | /**
54 | * {@inheritDoc}
55 | */
56 | public function isAccepted(ProjectInterface $project): bool
57 | {
58 | $project_info = $project->getRawMetadata();
59 | if (isset($project_info['namespace'])) {
60 |
61 | // Extra data from namespace to patch on.
62 | $valid_keys = [
63 | 'name' => 'name',
64 | 'id' => 'id',
65 | ];
66 | $namespace_info = array_intersect_key($project_info['namespace'], $valid_keys);
67 | $namespace_info = array_map('strtolower', $namespace_info);
68 |
69 | if (!empty($namespace_info) && !empty(array_intersect($namespace_info, $this->groups))) {
70 | // Accept any package with a permitted namespace name or id.
71 | return TRUE;
72 | }
73 | }
74 | return FALSE;
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/src/MBO/SatisGitlab/Resources/default-template.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "mborne/satis-gitlab-repository",
3 | "homepage": "https://satis.dev.localhost/",
4 | "repositories": [
5 | {
6 | "type": "composer",
7 | "url": "https://packagist.org"
8 | }
9 | ],
10 | "require": [],
11 | "require-dependencies": true,
12 | "require-dev-dependencies": true
13 | }
--------------------------------------------------------------------------------
/src/MBO/SatisGitlab/Satis/ConfigBuilder.php:
--------------------------------------------------------------------------------
1 | config = json_decode(file_get_contents($templatePath),true);
27 | }
28 |
29 | /**
30 | * Get resulting configuration
31 | */
32 | public function getConfig(){
33 | return $this->config;
34 | }
35 |
36 | /**
37 | * Set name
38 | */
39 | public function setName($name){
40 | $this->config['name'] = $name;
41 |
42 | return $this;
43 | }
44 |
45 | /**
46 | * Set homepage
47 | * @return $self
48 | */
49 | public function setHomepage($homepage){
50 | $this->config['homepage'] = $homepage;
51 |
52 | return $this;
53 | }
54 |
55 | /**
56 | * Turn on mirror mode
57 | * @return $self
58 | */
59 | public function enableArchive(){
60 | $this->config['archive'] = array(
61 | 'directory' => 'dist',
62 | 'format' => 'tar',
63 | 'skip-dev' => true
64 | );
65 | }
66 |
67 | /**
68 | * Add gitlab domain to config
69 | * @return $self
70 | */
71 | public function addGitlabDomain($gitlabDomain){
72 | if ( ! isset($this->config['config']) ){
73 | $this->config['config'] = array();
74 | }
75 | if ( ! isset($this->config['config']['gitlab-domains']) ){
76 | $this->config['config']['gitlab-domains'] = array();
77 | }
78 |
79 | $this->config['config']['gitlab-domains'][] = $gitlabDomain ;
80 |
81 | return $this;
82 | }
83 |
84 | /**
85 | * Add gitlab token
86 | *
87 | * TODO : Ensure addGitlabDomain is invoked?
88 | *
89 | * @return $self
90 | */
91 | public function addGitlabToken($gitlabDomain, $gitlabAuthToken){
92 | if ( ! isset($this->config['config']['gitlab-token']) ){
93 | $this->config['config']['gitlab-token'] = array();
94 | }
95 | $this->config['config']['gitlab-token'][$gitlabDomain] = $gitlabAuthToken;
96 |
97 | return $this;
98 | }
99 |
100 |
101 | /**
102 | * Add a repository to satis
103 | *
104 | * @param string $projectName "{vendorName}/{componentName}"
105 | * @param string $projectUrl
106 | * @param boolean $unsafeSsl allows to disable ssl checks
107 | *
108 | * @return $self
109 | */
110 | public function addRepository(
111 | $projectName,
112 | $projectUrl,
113 | $unsafeSsl = false
114 | ){
115 | if ( ! isset($this->config['repositories']) ){
116 | $this->config['repositories'] = array();
117 | }
118 |
119 | $repository = array(
120 | 'type' => 'vcs',
121 | 'url' => $projectUrl
122 | );
123 |
124 | if ( $unsafeSsl ){
125 | $repository['options'] = [
126 | "ssl" => [
127 | "verify_peer" => false,
128 | "verify_peer_name" => false,
129 | "allow_self_signed" => true
130 | ]
131 | ];
132 | }
133 |
134 | $this->config['repositories'][] = $repository ;
135 | $this->config['require'][$projectName] = '*';
136 | }
137 |
138 | }
--------------------------------------------------------------------------------
/tests/Command/GitlabToConfigCommandTest.php:
--------------------------------------------------------------------------------
1 | outputFile = tempnam(sys_get_temp_dir(),'satis-config');
20 | }
21 |
22 | protected function tearDown(): void
23 | {
24 | if ( file_exists($this->outputFile) ){
25 | unlink($this->outputFile);
26 | }
27 | }
28 |
29 | public function testWithFilter(){
30 | $gitlabToken = getenv('SATIS_GITLAB_TOKEN');
31 | if ( empty($gitlabToken) ){
32 | $this->markTestSkipped("Missing SATIS_GITLAB_TOKEN for gitlab.com");
33 | return;
34 | }
35 | $command = new GitlabToConfigCommand('gitlab-to-config');
36 | $commandTester = new CommandTester($command);
37 | $commandTester->execute(array(
38 | 'gitlab-url' => 'http://gitlab.com',
39 | 'gitlab-token' => $gitlabToken,
40 | '--projectFilter' => 'sample-composer',
41 | '--include-if-has-file' => 'README.md',
42 | '--output' => $this->outputFile
43 | ));
44 |
45 | $output = $commandTester->getDisplay();
46 | $this->assertStringContainsString(
47 | 'mborne/sample-composer',
48 | $output
49 | );
50 |
51 | /* check and remove gitlab-token */
52 | $result = file_get_contents($this->outputFile);
53 | $result = json_decode($result,true);
54 | $this->assertEquals($gitlabToken,$result['config']['gitlab-token']['gitlab.com']);
55 | $result['config']['gitlab-token']['gitlab.com'] = 'SECRET';
56 |
57 | /* compare complete file */
58 | $expectedPath = dirname(__FILE__).'/expected-with-filter.json';
59 | //file_put_contents($expectedPath,json_encode($result,JSON_PRETTY_PRINT));
60 | $this->assertJsonStringEqualsJsonFile(
61 | $expectedPath,
62 | json_encode($result,JSON_PRETTY_PRINT)
63 | );
64 | }
65 |
66 |
67 |
68 |
69 | }
70 |
71 |
--------------------------------------------------------------------------------
/tests/Command/expected-with-filter.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "mborne\/satis-gitlab-repository",
3 | "homepage": "https:\/\/satis.dev.localhost\/",
4 | "repositories": [
5 | {
6 | "type": "composer",
7 | "url": "https:\/\/packagist.org"
8 | },
9 | {
10 | "type": "vcs",
11 | "url": "https:\/\/gitlab.com\/mborne\/sample-composer.git"
12 | }
13 | ],
14 | "require": {
15 | "mborne\/sample-composer": "*"
16 | },
17 | "require-dependencies": true,
18 | "require-dev-dependencies": true,
19 | "config": {
20 | "gitlab-domains": [
21 | "gitlab.com"
22 | ],
23 | "gitlab-token": {
24 | "gitlab.com": "SECRET"
25 | }
26 | }
27 | }
--------------------------------------------------------------------------------
/tests/Satis/ConfigBuilderTest.php:
--------------------------------------------------------------------------------
1 | getConfig();
15 |
16 | // name
17 | $this->assertArrayHasKey('name',$result);
18 | $this->assertEquals('mborne/satis-gitlab-repository',$result['name']);
19 |
20 | // homepage
21 | $this->assertArrayHasKey('homepage',$result);
22 | $this->assertEquals('https://satis.dev.localhost/',$result['homepage']);
23 | }
24 |
25 | public function testSetName(){
26 | $configBuilder = new ConfigBuilder();
27 | $configBuilder->setName('acme/satis-repository');
28 | $result = $configBuilder->getConfig();
29 | // homepage
30 | $this->assertArrayHasKey('name',$result);
31 | $this->assertEquals('acme/satis-repository',$result['name']);
32 | }
33 |
34 | public function testSetHomepage(){
35 | $configBuilder = new ConfigBuilder();
36 | $configBuilder->setHomepage('http://satis.example.org');
37 | $result = $configBuilder->getConfig();
38 | // homepage
39 | $this->assertArrayHasKey('homepage',$result);
40 | $this->assertEquals('http://satis.example.org',$result['homepage']);
41 | }
42 |
43 | public function testEnableArchive(){
44 | $configBuilder = new ConfigBuilder();
45 | $configBuilder->enableArchive();
46 | $result = $configBuilder->getConfig();
47 |
48 | $this->assertArrayHasKey('archive',$result);
49 |
50 | $this->assertArrayHasKey('directory',$result['archive']);
51 | $this->assertEquals('dist',$result['archive']['directory']);
52 |
53 | $this->assertArrayHasKey('format',$result['archive']);
54 | $this->assertEquals('tar',$result['archive']['format']);
55 |
56 | $this->assertArrayHasKey('skip-dev',$result['archive']);
57 | $this->assertTrue($result['archive']['skip-dev']);
58 | }
59 |
60 | public function testAddGitlabDomain(){
61 | $configBuilder = new ConfigBuilder();
62 | $configBuilder->addGitlabDomain('gitlab.com');
63 | $configBuilder->addGitlabDomain('my-gitlab.com');
64 |
65 | $result = $configBuilder->getConfig();
66 |
67 | $this->assertArrayHasKey('config',$result);
68 |
69 | $this->assertEquals(
70 | '{"gitlab-domains":["gitlab.com","my-gitlab.com"]}',
71 | json_encode($result['config'])
72 | );
73 | }
74 |
75 | public function testAddGitlabToken(){
76 | $configBuilder = new ConfigBuilder();
77 | $configBuilder->addGitlabToken('gitlab.com','test');
78 |
79 | $result = $configBuilder->getConfig();
80 |
81 | $this->assertArrayHasKey('config',$result);
82 |
83 | $this->assertEquals(
84 | '{"gitlab-token":{"gitlab.com":"test"}}',
85 | json_encode($result['config'])
86 | );
87 | }
88 |
89 | public function testAddRepository(){
90 | $configBuilder = new ConfigBuilder();
91 | $configBuilder->addRepository(
92 | 'mborne/fake-a',
93 | 'https://github.com/mborne/fake-a.git',
94 | false
95 | );
96 | $configBuilder->addRepository(
97 | 'mborne/fake-b',
98 | 'https://github.com/mborne/fake-b.git',
99 | true
100 | );
101 |
102 | $satis = $configBuilder->getConfig();
103 |
104 | $this->assertArrayHasKey('repositories',$satis);
105 |
106 | $result = $satis['repositories'];
107 | /* compare complete file */
108 | $expectedPath = dirname(__FILE__).'/expected-repositories.json';
109 | //file_put_contents($expectedPath,json_encode($result,JSON_PRETTY_PRINT));
110 | $this->assertJsonStringEqualsJsonFile(
111 | $expectedPath,
112 | json_encode($result,JSON_PRETTY_PRINT)
113 | );
114 | }
115 |
116 |
117 | }
118 |
119 |
--------------------------------------------------------------------------------
/tests/Satis/expected-repositories.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "type": "composer",
4 | "url": "https:\/\/packagist.org"
5 | },
6 | {
7 | "type": "vcs",
8 | "url": "https:\/\/github.com\/mborne\/fake-a.git"
9 | },
10 | {
11 | "type": "vcs",
12 | "url": "https:\/\/github.com\/mborne\/fake-b.git",
13 | "options": {
14 | "ssl": {
15 | "verify_peer": false,
16 | "verify_peer_name": false,
17 | "allow_self_signed": true
18 | }
19 | }
20 | }
21 | ]
--------------------------------------------------------------------------------
/tests/TestCase.php:
--------------------------------------------------------------------------------
1 | getMockBuilder(ProjectInterface::class)
19 | ->getMock()
20 | ;
21 | $project->expects($this->any())
22 | ->method('getName')
23 | ->willReturn($projectName)
24 | ;
25 | return $project;
26 | }
27 |
28 | }
--------------------------------------------------------------------------------