Project Manager

Dave Redfern

Published: 21 Feb 01:20 in Tools

Introduction

Somnambulist Project Manager

GitHub Actions release Build Status

Project Manager is designed to help organise micro-services based PHP projects. It incorporates commands for creating new services, libraries, and managing them. Project configuration is stored as YAML files that you commit to a git repo and share with your team. If you want to stop using it, delete the phar and the files and continue on. There is no special configuration needed in any project.

Features

  • groups separate micro services projects together
  • gives a docker overview of running processes
  • supports dependencies between services
  • supports making new services and libraries (configurable source repositories)
  • supports multiple projects on a single machine
  • all configuration is by yaml files

Setup

Grab the phar archive and copy it to /usr/local/bin or add it to your path. Symlink the phar to spm or a.n.other name. Be sure to verify the SHA checksum with the ones in the release checksums text file.

Run: spm init to create the standard configuration (see later).

Or: brew install somnambulist-tech/somnambulist/spm

Removing Project Manager

If installed via brew: brew remove somnambulist-tech/somnambulist/spm

Remove any symlinks you have and delete the phar file. Delete the configuration folder from ~/.config/spm_projects.d/ or ~/.spm_projects.d/

unlink /usr/local/bin/spm && rm -v /usr/local/bin/somnambulist-project-manager.phar

Building the phar archive

To build the phar archive, first checkout / clone the project manager project. Run composer install and ensure that phar.readonly is set to 0 in your php.ini.

You can then run: bin/compile which will create a somnambulist-project-manager.phar file in the project root. The compile will output the SHA384 hash together with the file location / name.

Getting Started

Getting Started

Project Manager (spm from here), works through a set of config files that are stored in the primary spm config folder. By default this is in ~/.config/spm_projects.d. When you first run spm it will prompt to run: spm init if this folder does not exist.

Note: spm will use XDG_CONFIG_HOME if defined, otherwise will default to ~/.config as per the XDG Base Directory spec. You can update existing configurations by moving the folder to ~/.config/spm_projects.d. The older ~/.spm_projects.d is still supported.

The base folder can be changed by defining the env var: SOMNAMBULIST_PROJECT_MANAGER_DIR. Please note that spm expects all config to still be located within your home folder. Once running SOMNAMBULIST_PROJECTS_CONFIG_DIR is used as the fully qualified path to the spm configuration folder.

By default projects are expected to be organised in ~/Projects/<project_name>. The projects default folder can be changed by editing the project_manager.yaml file and changing the projects_dir.

Please note that this folder should exist within your home folder. spm does not support folders that are not located within your current users home as it relies heavily on the $_ENV['HOME'] variable to determine paths.

The default config file looks like:

somnambulist:
    cache_dir: '${SOMNAMBULIST_PROJECTS_CONFIG_DIR}/_cache'
    projects_dir: 'Projects'

    templates:
        library:
            bundle: ~
            client: ~
            library: ~

        service:
            data: 'git:git@github.com:somnambulist-tech/data-service-skeleton.git'
            logging: 'git:git@github.com:somnambulist-tech/logging-service-skeleton.git'
            api: 'git:git@github.com:somnambulist-tech/web-api-skeleton.git'
            app: 'git:git@github.com:somnambulist-tech/web-app-skeleton.git'
            web: 'composer:symfony/skeleton'
            symfony: 'composer:symfony/skeleton'

You can check the current spm setup by using spm check. This will show you all the current configuration locations and if the configuration was initialised properly. Add --debug or -d to output the config and env file contents if they exist.

Terminal and IDE integration

spm includes two helpers to make navigating a project easier: open and goto. open will open an IDE (default PhpStorm) with the specified library and goto will start a new terminal session at the library. If no library (library here being a library or a service) is specified, a list of all options for the project will be presented.

In both cases the script / IDE can be configured by setting the following ENV vars in your .bashrc or .zshrc or shell init file:

  • SOMNAMBULIST_EDITOR=XXXX - to override the PHP IDE. This should support a CLI command: e.g. atom
  • SOMNAMBULIST_TERMINAL_SCRIPT - a script that can open a new terminal at a path

By default, goto expects to work on macOS and uses osascript to launch a new terminal session.

Projects

Project Configuration

spm works with projects. A project is a collection of libraries and services. The project configuration defines what libraries and services make up the project and the repositories where they can be located.

A library is a package / bundle that is used by multiple services. Typically libraries are things like API clients for the services but can include project skeletons or other repos.

A service is an application that will be run within docker. Typically this will be a micro service, but it could be the data services, a web site etc. Essentially anything that will run in Docker.

In the case of services; spm can provide an overview of what is currently running and what ports / domains have been exposed. This requires Docker CLI be installed. Provided your Docker environment is correctly configured, spm will work with remote docker hosts.

To create a new project run: spm project:create or spm create. You will be prompted for the name (basically the folder the config will be stored in) and then if you have a remote Git repository already. If you do, provide it and it will be checked out immediately; otherwise leave it blank and then provide the global docker compose project name. This is important as it will be the prefix used to determine the container names of the services. It should be relatively unique but not overly long e.g.: the company name, or project name.

For example:

 bin/console create
 Q  What is your projects name? Use lowercase and underscores: example
 ▲  You provided "example", is this correct? [y/n] y
 Q  What is your remote git repo to load config data from? Leave blank to skip:   
 ▲  You provided "", is this correct? [y/n] y
 Q  What will be your Docker Compose name? Use lowercase and hyphens: example
 ▲  You provided "example", is this correct? [y/n] y
 ▲  created configuration directory at /Users/dave/.spm_projects.d/example
 ▲  created configuration file at /Users/dave/.spm_projects.d/example/project.yaml
 ▲  creating git repository at /Users/dave/.spm_projects.d/example
 ✔  created git repository at /Users/dave/.spm_projects.d/example
 ✔  project created successfully, enable the project by running: use example

Standard Project Config

The generated config file has the following default structure:

somnambulist:
    project:
        name: 'example'
        repository: ~
        working_dir: '${HOME}/Projects/example'
        libraries_dirname: ~
        services_dirname: ~

    docker:
        compose_project_name: 'example'

    libraries:

    services:
    
    templates:
        libraries:
        
        services:

The config file will expand any configured env args using ${ENV_NAME} notation. Note that these may be committed back so be careful. The home path will automatically be replaced with ${HOME} provided it is available.

Project

The project section contains the top level information about the project itself. The repository is the remote repo of the project data and should be set if you wish to share the project configuration.

Libraries and Services by default are stored together within the project working directory. If you have many libraries and/or services you may wish to separate them logically into sub-folders. Specify the folder name you wish to use, and then any service/library will be added to that folder if installed or created.

Docker

The docker config contains various settings for docker. Currently this is only the project name however any other key: value pairs can be provided.

Note: in a future version this configuration may be used when creating services.

Libraries

Lists the configured libraries that are part of this project. A library config has:

  • name - the name within the project (must be unique)
  • repository - the remote git repository (can be null)
  • dirname - the local checkout folder name

For example:

somnambulist:
    libraries:
        api_client:
            repository: 'some git repo'
            dirname: 'api-client'

Services

Services is the set of docker applications that belong to this project. A service has:

  • name - the name within the project (must be unique)
  • repository - the remote git repository (can be null)
  • dirname - the local checkout folder name
  • app_container - the name of the primary application container (should be relatively unique)
  • dependencies - an array of service names this service depends on

For example:

somnambulist:
    services:
        cms-service:
            repository: 'some git repo'
            dirname: 'cms-service'
            app_container: 'cms-app'
            dependencies: ['data']

Templates

Templates allow for rapidly scaffolding new libraries / services. By default the following services templates are pre-configured globally:

Templates are grouped by type: library and service. Only library types are displayed when making new libraries, and the same goes for services.

These can be overridden globally (in the main ~/.spm_projects.d/project_manager.yaml) or on a per-project basis in the templates section.

The source can be one of:

  • composer:
  • git:
  • in the project config templates folder
  • empty
Composer Templates

composer: will use composer create-project and requires that the source be a valid installation project either registered with packagist.org or with private packagist.com.

To use a custom repository like with Private Packagist add ?repository=https://repo/source.

To specify a specific version to use add &version=XXX. To use the latest version set the version to dev-master.

The full template source would then look like: composer:namespace/project-name?repository=https://some.repo/somewhere&version=2.0.2.

Git Templates

git: will clone and remove the .git folder, essentially using the git repo as a template.

Static Templates

<folder_name> will copy all files in that folder to the new source. Additionally, if the template folder contains a post_copy.php file, this will be run after the files have been copied. This script can perform any actions needed for setup. Further: if a post_copy_args.php exists, then for each argument, a question will be asked for input that is then provided to the script. The format of the args is to return an array of key => values that. For example:

<?php
return [
    'name' => 'What is your name? ',
    'dob'  => 'What is your date of birth (CCYY-mm-dd)? ',
];

The post_copy.php file will be executed in a separate process in the context of the folder that was created. If the return type of the args is not an array nothing will be asked.

The post_copy.php file should check the arg inputs before running.

Generic Template (fallback)

If the template is left empty then a very basic folder is created with some general defaults including:

  • .gitignore
  • readme.md
  • src/tests folder
  • composer.json
  • phpunit.xml.dist

A blank service template includes a few more files for docker settings.

Adding libraries / services

First ensure you have created a project, then switch to that project using spm use <project>.

Now you can run spm services:create or spm libraries:create to create one of those types. Once completed the library will be automatically registered in the project config and it will be updated.

By default a new git repository is initialised in the library folder and all files committed to the master branch.

If you have existing libraries, manually configure them in the project.yaml file or you can try the auto-importer: spm project:import. This will attempt to allocate folders to either a library or service and will try to match an active container name for the app. The rules for container names are:

  • name contains -app
  • the labels contain traefik.frontend.rule and the traefik.port is 8080

The import does not try to determine dependencies and will use the folder name as the project and dirname values.

Working with Services

Working with services

Starting

spm provides several wrappers to help with starting / stopping and getting an overview of the current projects services. All services commands are prefixed with services:. Once you have added some services you can list them: spm services:list and then start one or more: spm services:start service1 service2 service3 or start all of the services: spm services:start all. services:start is aliased to start

If you have defined dependencies and have not specified either -d to automatically start dependencies, or -D to not start dependencies; you will be prompted if you wish to start the dependencies first or not. If you opt to start services, then all dependencies will be resolved and started first. You will not be prompted again after being asked the first time.

Status Overview

To get an overview of the current project services: spm services:status. This will query Docker to get assorted data about all containers that match the preset compose name from the project config and display the information in a table. This information includes:

  • running container name
  • current status (up/down etc)
  • the host if it has been set e.g. for traefik or the IP for the external DB connection
  • external port(s)
  • any volumes connected to the container

The status output is in a CLI table by default, however it can also be generated as:

  • CSV
  • JSON
  • pipe separated, plain text

Add --format=csv|json|plain to get the desired output.

If SyncIt is installed and setup on a service; the current status will be output with the spm status information. Note: this adds an amount of overhead and can take a few seconds to display for many running containers. To disable syncit checks, add --no-syncit.

Stopping

To stop a service use: spm services:stop service1 service2 or spm services:stop all to stop all running services. Services that have dependencies will automatically cause dependent services to be stopped as well. For example: if you have a main data service that provides databases, and your apps depend on this; when you stop the service, then all the dependent services will be stopped first. services:stop is aliased to stop.

If syncit is installed and there are active sync sessions, they will be stopped before the container the service is stopped.

Logs

The docker logs can be viewed by running: spm services:log service or use the alias log. Add -f to follow the log and use Ctrl+C to stop. This is the same output that you can get from: docker-compose log <container-name>.

Rebuilding / cleaning the docker containers

If you encounter major issues with your containers or just want to reset to a completely clean state, either use: docker system prune --all --volumes or spm services:reset. The spm command calls the same options under-the-hood.

For less drastic rebuilds, the services:start command allows for:

  • --rebuild / -b - rebuild containers and then start
  • --refresh / -r - refresh and start

The difference between build and refresh is that refresh will force pull any new images as well as rebuild the container images. Use this if the upstream image has been updated and you need a new version instead of the cached version.

Copy files to/from containers

You can easily copy files to/from your running containers by using: spm services:copy that is aliased as copy and cp. Note: that one side of this command must be a service name specified as service_name:/path/in/container.

The format is: spm services:copy source target, for example to copy a file from your downloads into the users-app container, configured as users:

spm services:copy ~/Downloads/file.txt users:/app/tmp/file.txt

Under-the-hood, services:copy uses docker cp.

Note: the copy command uses the service name; not the actual docker container name!

Note: the copy command uses the current working directory and not the services path when it is running. Run spm services:copy -h for help and the current working folder.

Setting Config Options

Changing Configuration Options

spm allows configuration directives to be changed from the command line (or you can edit the yaml files as needed). Run spm config to get a list of what you can change. Type completion is provided making it easy to select the options. At the time of writing the options are:

$ spm config
 Q  Select the option to change 
  [docker:name              ] Set the docker compose project name
  [docker:network           ] Set the docker shared network name
  [git:remote               ] Set the remote repository for the project/library/service
  [service:container:name   ] Change the name of the services main container (used for detection)
  [service:dependency:add   ] Add a dependency to the service
  [service:dependency:remove] Remove a dependency from the service
  [service:rename           ] Rename an existing services alias
  [template:add             ] Change a project template source (specify as type:name:source)
  [template:remove          ] Remove a project template

Once an option is selected, you will be prompted for the service to apply changes to. Then you will either be prompted for further information or enter the value. Take care to read what is expected. To cancel at any point hit: Ctrl+C

Setting Remote Repositories

By default when using project:create, libraries:create or services:create a git repository is started, but no remote is set. You can set this after the fact by using:

spm config git:remote <project_name> <repo>

Several other config options can be changed using the config command:

  • docker:name
  • docker:network
  • git:remote
  • service:container:name
  • service:dependency:add
  • service:dependency:remove
  • template:add
  • template:remove

If the option is not provided it will be prompted for; similarly if the project name is not specified, the current list of projects will be presented.

Note: when changing the remote repository, only a remote named origin will be modified. If you used a different name, you must manually change the remote and update the project config file yourself.

Scripting Setup

Script setting up a development environment

spm includes a setup:env command that will read a YAML file with various setup instructions intended to get a new user up and running very quickly with the project. This file can include any number of shell commands to set various aspects on the machine. The file must be named: init_[mac|linux|windows].yaml. This command is aliased to: init:mac, init:linux and init:windows.

For example: you can create steps that install brew, and additional packages; or that creates overrides / default config files in various places.

The config format is pretty simple:

somnambulist:
    steps:
        max_files:
            message: 'Setting max open files override (requires sudo)'
            commands:
                - { run: "echo '%s' | sudo tee /Library/LaunchDaemons/limit.maxfiles.plist", file: 'init.d/limit.maxfiles.plist' }

Each step should have a unique name. You must provide a message and then one or more commands that need to be run in this step. Steps can use sudo and can read files in via echo. A command must have a run property, and an optional file for the file source. This file must be located in the project config folder.

The following environment variables can be used and will be replaced with the appropriate value:

  • ${CONFIG_DIR} - the project configuration folder, usually: ~/.spm_projects.d/<name>
  • ${HOME} - the current users home folder, usually: ~/ (/Users/)
  • ${CWD} - the current working directory from where spm was run (_SERVER['PWD'])

If any step fails, the setup halts.

The setup can be run in test mode --test or output all the expanded scripts by using --bash. Various steps can be skipped if already applied by using --skip=X where X is the number of the step.

If a particular operation requires a long time or runs as a desktop process (e.g. xcode-select) a special exit command can be added that will automatically halt the setup and output the instructions on how to continue.

If debugging is used -vvv then each command and it's output will be streamed to the console.

When developing a setup script, be sure to do it in a virtual machine to avoid damaging your own setup.

Return to article