diff --git a/.goreleaser.yml b/.goreleaser.yml index 8f5e3420..6863fb70 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -2,7 +2,6 @@ # Make sure to check the documentation at http://goreleaser.com before: hooks: - # You may remove this if you don't use go modules. - go mod download builds: - binary: nitro @@ -18,7 +17,7 @@ builds: archives: - name_template: "{{.ProjectName}}_{{.Os}}_{{.Arch}}" - id: example + id: nitro builds: - nitro replacements: diff --git a/CHANGELOG.md b/CHANGELOG.md index 5b47c189..b4ecee70 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,37 @@ -# Release Notes for Nitro +# Release Notes for Craft Nitro ## Unreleased +## 0.10.0 - 2020-04-23 + +> **Warning:** This release contains breaking changes. See the [upgrade notes](UPGRADE.md#upgrading-to-nitro-0100) +> for details. + +## Added +- Added the `init` command, which initializes new machines. +- Added the `remove` command, which removes a site from a machine. + +### Changed +- All machine configs are now stored saved in `~/.nitro/`. +- All commands now have an `-m` option, which can be used to specify which machine to work with. (The `-f` option + has also been removed.) +- The `apply` command now creates any new database servers that it finds in the config file. +- The `machine destroy` command has been renamed to `destroy`, and it now permanently destroys the machine (as + opposed to archiving it), and removes any hostnames added to your hosts file that point to its IP address. +- The `machine restart` command has been renamed to `restart`. +- The `machine start` command has been renamed to `start`. +- The `machine stop` command has been renamed to `stop`. +- Renamed `get.sh` to `install.sh`. + +### Removed +- Removed the `machine create` command. Use the new `init` command to create new machines instead. + +### Fixed +- Fixed a bug where users could get a segfault when adding a site. ([#78](https://github.com/craftcms/nitro/issues/78)) +- Fixed a bug where it wasn’t possible to import databases using relative paths. ([#75](https://github.com/craftcms/nitro/issues/75)) +- Fixed a bug where the `machine create` command listed MySQL 5.8 as an option. +- Fixed a bug where php-fpm wouldn’t restart after running the `xdebuf off` command. + ## 0.9.3 - 2020-04-20 ### Fixed @@ -29,4 +59,4 @@ ## 0.7.5 - 2020-04-12 ### Added -- Added checksum support for `get.sh` when downloading and updating. ([#56](https://github.com/craftcms/nitro/issues/56)) \ No newline at end of file +- Added checksum support for `get.sh` when downloading and updating. ([#56](https://github.com/craftcms/nitro/issues/56)) diff --git a/Makefile b/Makefile index 1d518c6f..9ae98441 100644 --- a/Makefile +++ b/Makefile @@ -1,23 +1,11 @@ .PHONY: install -BUDDY_EXECUTION_TAG ?= 0.8.0 -MACHINE ?= nitro-global +VERSION ?= 0.8.0 build: - go build -ldflags="-s -w -X 'github.com/craftcms/nitro/internal/cmd.Version=${BUDDY_EXECUTION_TAG}'" -o nitro ./cmd/cli + go build -ldflags="-s -w -X 'github.com/craftcms/nitro/internal/cmd.Version=${VERSION}'" -o nitro ./cmd/cli +local: build + mv nitro /usr/local/bin/nitro test: go test ./... -demo-site: - composer create-project craftcms/craft demo-site releaser: goreleaser --skip-publish --rm-dist --skip-validate -integration-test: build - ./nitro -f nitro.yaml machine create - composer create-project craftcms/craft demo-site - sudo ./nitro -f nitro.yaml hosts -remove-integration-test: - ./nitro -f nitro.yaml machine destroy -p - rm -rf demo-site -test-version: build - ./nitro version -test-version-releaser: releaser - diff --git a/README.md b/README.md index f30d33ad..814c5b14 100644 --- a/README.md +++ b/README.md @@ -2,24 +2,18 @@ # Craft Nitro -Nitro is a command-line tool focused on making local Craft development quick and easy. Nitro’s one dependency is [Multipass](https://multipass.run/), which allows you to create Ubuntu virtual machines. +Nitro is a command-line tool focused on making local Craft development quick and easy. Nitro’s one dependency is +[Multipass](https://multipass.run/), which allows you to create Ubuntu virtual machines. ---- - -## Table of Contents - -- [Requirements](#requirements) - [What’s Included](#whats-included) - [Installation](#installation) -- [Getting Started](#getting-started) +- [Adding Sites](#adding-sites) +- [Adding Mounts](#adding-mounts) +- [Running Multiple Machines](#running-multiple-machines) - [Commands](#commands) --- -## Requirements - -- [Multipass](https://multipass.run) - ## What’s Included Nitro installs the following on every machine: @@ -31,68 +25,110 @@ Nitro installs the following on every machine: - Xdebug - Blackfire -> Note: For a more detailed writeup on how to configure Xdebug and Nitro with PhpStorm, view this document on [how to configure Xdebug and PhpStorm for both web and console debugging](XDEBUG.md). +> Note: For a more detailed writeup on how to configure Xdebug and Nitro with PhpStorm, view this document on +> [how to configure Xdebug and PhpStorm for both web and console debugging](XDEBUG.md). ## Installation -``` -curl https://raw.githubusercontent.com/craftcms/nitro/master/get.sh | sudo sh -``` +1. Install [Multipass](https://multipass.run) (requires 1.2.0+). +2. Run this terminal command: -## Getting Started + ```sh + curl -sLS http://installer.getnitro.sh | bash + ``` -In order to create a new development server, you must create a new Nitro machine. By default, this will not attach any directories and is equivalent to getting a brand new Virtual Private Server (VPS). +3. Follow the prompts to create your machine. -Nitro defaults to a global `nitro.yaml`. The default location is `~/.nitro/nitro.yaml`. However, you can override the configuration for each command by providing a `--config` (or the shorthand `-f`) with the path to the file (e.g. `nitro -f /path/to/nitro.yaml `). Here is an example config: +Once complete, you will have a Multipass machine called `nitro-dev`, and a new configuration file for the machine + stored at `~/.nitro/nitro-dev.yaml`. -```yaml -name: diesel -php: "7.4" -cpus: "2" -disk: 40G -memory: 4G -databases: -- engine: mysql - version: "5.7" - port: "3306" -- engine: postgres - version: "12" - port: "5432" -``` +## Adding Sites + +To add a site to Nitro, three things need to happen: + +- Your project files need to be [mounted](#adding-mounts) into the Nitro machine. +- The web server within your Nitro machine needs to be configured to serve your site. +- Your system’s `hosts` file needs to be updated to associate your site’s hostname with Nitro. -This works like you might expect, it will create a new machine named `diesel` with 2 CPUs and 4GB of RAM. In addition, it will create two database "servers" inside the virtual machine for MySQL and PostgreSQL and make the assigned ports available on the machine's IP address (i.e. not localhost). +### Add a site with `nitro add` -> Note: Nitro can run multiple versions of the same database engine (e.g. PostgreSQL 11 and 12) because it utilizes Docker underneath. See [this file](examples/nitro-multiple-versions.yaml) for an example. +If your project files are completely contained within a single folder, then you can quickly accomplish these using +the [`add`](#add) command: -```bash -nitro machine create +```sh +$ cd /path/to/project +$ nitro add +→ What should the hostname be? $ example.test +→ Where is the webroot? $ web +✔ example.test has been added to nitro.yaml. +→ apply nitro.yaml changes now? $ yes +✔ Applied the changes and added example.test to nitro-dev +Adding nitro-dev to your hosts file +Password: +✔ example.test added successfully! ``` -> Note: If you run `nitro machine create` and it cannot locate the `nitro.yaml` it will walk you through setting up the machine. +### Add a site manually -After running `machine create`. The bootstrap process will install the latest PHP version, MySQL, Postgres, and Redis from the `nitro.yaml` file. +If you would prefer to add a site manually, follow these steps: -The next step is to add a new site to the machine: +1. Open your `~/.nitro/nitro-dev.yaml` file in a text editor, and add a new [mount](#adding-mounts) and site to it: -```bash -cd /Users/jasonmccallister/dev -$ nitro add my-project -→ What should the hostname be? [my-project] $ myproject.test -→ Where is the webroot? [web] $ -myproject.test has been added to nitro.yaml. -→ apply nitro.yaml changes now? [y] $ n -You can apply new nitro.yaml changes later by running `nitro apply`. -``` + ```yaml + mounts: + - source: /path/to/project + dest: /nitro/sites/example.test + sites: + - hostname: example.test + webroot: /nitro/sites/example.test/web + ``` + +2. Run `nitro apply` to apply your `nitro.yaml` changes to the machine. You will be prompted for your password so + Nitro can add the new hostname to your system’s `hosts` file. + +You should now be able to point your web browser at your new hostname. + +## Adding Mounts + +Nitro can mount various system directories into your Nitro machine. You can either mount each of your projects’ +root directories into Nitro individually (as you’d get when [adding a site with `nitro +add`](#add-a-site-with-nitro-add)), or you can mount your entire development folder, or some combination of the two. + +To add a new mount, follow these steps: + +1. Open your `~/.nitro/nitro.yaml` file in a text editor, and add the new mount: + + ```yaml + mounts: + - source: /Users/cathy/dev + dest: /nitro/dev + ``` + +2. Run `nitro apply` to apply the `nitro.yaml` change to the machine. + +Once that’s done, yous should be able to tunnel into your machine using the [`ssh`](#ssh) command, and see the +newly-mounted directory in there. + +## Running Multiple Machines -> Note: You can use any top-level domain you wish, but we recommend using .test +You can have Nitro manage more than just your primary machine (`nitro-dev`) if you want. For example, you could +create a machine for a specific dev project. -This process will perform the following tasks: +To create a new machine, run the following command: -1. Set up a new nginx virtual server for `myproject.test`. -2. Attach the directory `/Users/jasonmccallister/dev/my-project` to that virtual server. -3. Edit your `/etc/hosts` file to point `myproject.test` to the virtual server for use locally. +```sh +$ nitro init -m +``` -You can now visit `http://myproject.test` in your browser! +Replace `` with the name you want to give your new machine. Machine names can only include letters, +numbers, underscores, and hyphen. + +This command will run through the same prompts you saw when creating your primary machine after you first installed +Nitro. Once it’s done, you’ll have a new Multipass machine, as well as a new configuration file for it at +`~/.nitro/.yaml`. + +All of Nitro’s [commands](#commands) accept an `-m` option, which you can use to specify which machine the command +should be run against. (`nitro-dev` will always be used by default.) ## Commands @@ -101,15 +137,18 @@ The following commands will help you manage your virtual server. - [`apply`](#apply) - [`add`](#add) - [`context`](#context) +- [`destroy`](#destroy) - [`edit`](#edit) - [`info`](#info) +- [`init`](#init) - [`import`](#import) - [`logs`](#logs) -- [`machine create`](#machine-create) -- [`machine destroy`](#machine-destroy) +- [`remove`](#remove) - [`redis`](#redis) +- [`restart`](#restart) - [`self-update`](#self-update) - [`ssh`](#ssh) +- [`start`](#start) - [`stop`](#stop) - [`update`](#update) - [`version`](#version) @@ -117,17 +156,24 @@ The following commands will help you manage your virtual server. - [`xdebug on`](#xdebug-on) - [`xdebug off`](#xdebug-off) -> Note: These examples use a custom config file `nitro-example.yaml`. If you’d like to use Nitro’s default server name (`nitro-local`), you can skip adding the `--machine` argument. - ### `apply` -`apply` will look at a config file and make changes from the mounts and sites in the config file by adding or removing them. The config file is the source of truth for your Nitro machine. +Ensures that the machine exists, and applies any changes in its config file to it. -```bash -nitro apply +```sh +nitro apply [] ``` -```bash +Options: + +
+
-m, --machine
+
The name of the machine to use. Defaults to nitro-dev.
+
+ +Example: + +```sh $ nitro apply There are 2 mounted directories and 1 new mount(s) in the config file. Applied changes from nitro.yaml. @@ -135,43 +181,59 @@ Applied changes from nitro.yaml. ### `add` -Add will create an interactive prompt to add a site (and mount it) into your Nitro machine. By default, it will look at your current working directory and assume that it is a Craft project. +Adds a new site to the machine. -```bash -cd /Users/brandon/Sites/example.test -$ nitro add -→ What should the hostname be? [example.test] $ ex.test -→ Where is the webroot? [web] $ -ex.test has been added to nitro.yaml. -→ apply nitro.yaml changes now? [y] $ n -You can apply new nitro.yaml changes later by running `nitro apply`. +```sh +nitro add [] ``` -You can optionally pass a path to the directory as the first argument to use that directory: +Options: + +
+
-m, --machine
+
The name of the machine to use. Defaults to nitro-dev.
+
--hostname
+
The hostname to use for accessing the site. If not passed, the command will prompt for it.
+
--webroot
+
The relative path to the site’s webroot. If not passed, the command will prompt for it.
+
-```bash -cd /Users/brandon/Sites/ -$ nitro -f nitro.yaml add demo-site -✔ What should the hostname be? [demo-site]: $ -Where is the webroot? [web]: $ -✔ apply nitro.yaml changes now? [y]: $ -Applied the changes and added demo-site to nitro. -```` +Example: -| Argument | Default | Options | Description | -|--------------|------------------------------------------------|---------|---------------------------------------------| -| `--hostname` | (the current working directory name) | | The hostname to use for accessing the site. | -| `--webroot` | (looks for web, public, public_html, and www)) | | The webroot to configure nginx. | +```sh +$ cd /path/to/project +$ nitro add +→ What should the hostname be? $ example.test +→ Where is the webroot? $ web +✔ example.test has been added to nitro.yaml. +→ apply nitro.yaml changes now? $ yes +✔ Applied the changes and added example.test to nitro-dev +Adding nitro-dev to your hosts file +Password: +✔ example.test added successfully! +``` ### `context` -Shows the currently used configuration file for quick reference. +Shows the machine’s configuration. + +```sh +nitro contex [] +``` + +Options: -```shell -$ nitro -f nitro-example.yaml context -Using config file: nitro.yaml +
+
-m, --machine
+
The name of the machine to use. Defaults to nitro-dev.
+
+ +Example: + +```sh +$ nitro context +Machine: nitro-dev ------ -name: nitro php: "7.4" cpus: "1" disk: 40G @@ -192,11 +254,40 @@ sites: ------ ``` +### `destroy` + +Destroys a machine. + +```sh +nitro destroy [] +``` + +Options: + +
+
-m, --machine
+
The name of the machine to use. Defaults to nitro-dev.
+
+ ### `edit` -Edit allows you to quickly open your nitro.yaml file to manually make changes. However, it is recommended to use `nitro` commands to edit your config. +Edit allows you to quickly open your machine configuration to make changes. However, it is recommended to use +`nitro` commands to edit your config. + +```sh +nitro edit [] +``` + +Options: + +
+
-m, --machine
+
The name of the machine to use. Defaults to nitro-dev.
+
-```shell +Example: + +```sh nitro edit ``` @@ -204,9 +295,22 @@ nitro edit Shows the _running_ information for a machine like the IP address, memory, disk usage, and mounts. -```shell +```sh +nitro info [] +``` + +Options: + +
+
-m, --machine
+
The name of the machine to use. Defaults to nitro-dev.
+
+ +Example: + +```sh $ nitro info -Name: nitro +Name: nitro-dev State: Running IPv4: 192.168.64.48 Release: Ubuntu 18.04.4 LTS @@ -219,11 +323,50 @@ Mounts: /Users/jasonmccallister/sites/demo-site => /nitro/sites/demo-sit GID map: 20:default ``` +### `init` + +Initializes a machine. + +```sh +nitro init [] +``` + +Options: + +
+
-m, --machine
+
The name of the machine to use. Defaults to nitro-dev.
+
--php-version
+
The PHP version to use. If not passed, the command will prompt for it.
+
--cpus
+
The max number of CPUs that the machine can use. If not passed, one CPU will be used by default.
+
--memory
+
The max amount of system RAM that the machine can use. If not passed, the command will prompt for it.
+
--disk
+
The max amount of disk space that the machine can use. If not passed, the command will prompt for it.
+
+ +If the machine already exists, it will be reconfigured. + ### `import` -Import allows you to import a SQL file into a database. You will be prompted with a list of running database engines (mysql and postgres) to import the file into. +Import a SQL file into a database in the machine. You will be prompted with a list of running database engines +(MySQL and PostgreSQL) to import the file into. + +```sh +nitro import [] +``` + +Options: + +
+
-m, --machine
+
The name of the machine to use. Defaults to nitro-dev.
+
+ +Example: -```shell +```sh $ nitro import mybackup.sql Use the arrow keys to navigate: ↓ ↑ → ← ? Select database: @@ -232,103 +375,141 @@ Use the arrow keys to navigate: ↓ ↑ → ← ### `logs` -Views virtual machines logs. This command will prompt you for a type of logs to view (e.g. `nginx`, `database`, or `docker` (for a specific container)). +Views the machine’s logs. This command will prompt you for a type of logs to view, including e.g. `nginx`, +`database`, or `docker` (for a specific container). -```​shell -nitro logs +```sh +nitro logs [] ``` -### `machine create` - -Creates a new server. The following options are available: - -| Argument | Default | Options | Description | -|-----------------|---------|-----------------------------------|---------------------------------------------------| -| `--php-version` | `7.4` | `7.4`, `7.3`, `7.2`, `7.1`, `7.0` | Specifies PHP version used for bootstrap command. | -| `--cpus` | `2` | max host CPUs\* | Number of CPUs to allocate to the server. | -| `--memory` | `2G` | max host memory\* | Gigabytes of memory to allocate to the server. | -| `--disk` | `20G` | max host disk\* | Disk space to allocate to the server. | +Options: -\*: CPU, memory, and disk are shared with the host—not reserved—and represent maximum resources to be made available. +
+
-m, --machine
+
The name of the machine to use. Defaults to nitro-dev.
+
+### `remove` -### `machine destroy` +Removes a site from the machine. -Destroys a machine. +```sh +nitro remove [] +``` -> Note: By default, Multipass does not permanently delete a machine and can cause name conflicts (e.g. `instance "nitro-local" already exists`). This will not affect any local files or directories attached to the machine. +You will be prompted to select the site that should be removed. If the site has a corresponding +[mount](#adding-mounts) at `/nitro/sites/`, the mount will be removed as well. Options: -- `--permanent` permanently deletes a machine **(this is non-recoverable!)** +
+
-m, --machine
+
The name of the machine to use. Defaults to nitro-dev.
+
+ +### `redis` -To soft-destroy the `diesel` machine, so it is recoverable later: +Starts a Redis shell. -```bash -nitro machine destroy +```sh +nitro redis [] ``` -To **permanently** destroy the `diesel` machine: +Options: -```bash -nitro machine destroy --permanent -``` +
+
-m, --machine
+
The name of the machine to use. Defaults to nitro-dev.
+
-### `redis` +### `start` -Access a Redis shell. +Starts the machine. -```bash -nitro redis +```sh +nitro start [] ``` -### `start` +Options: + +
+
-m, --machine
+
The name of the machine to use. Defaults to nitro-dev.
+
+ +### `stop` -Starts, or turns on, a machine. +Stops the machine. -```bash -nitro start +```sh +nitro stop [] ``` -### `stop` +Options: + +
+
-m, --machine
+
The name of the machine to use. Defaults to nitro-dev.
+
+ +### `restart` -Stops, or turns off, a machine. +Restarts a machine. -```bash -nitro stop +```sh +nitro restart [] ``` +Options: + +
+
-m, --machine
+
The name of the machine to use. Defaults to nitro-dev.
+
+ ### `self-update` Perform updates to the Nitro CLI. -```bash +```sh nitro self-update ``` ### `ssh` -Nitro gives you full root access to your virtual server. The default user is `ubuntu` and has `sudo` permissions without a password. Once you’re in the virtual server, you can run `sudo` commands as usual (e.g. `sudo apt install golang`). +Tunnels into the machine as the default `ubuntu` user over SSH. -```bash -nitro ssh +```sh +nitro ssh [] ``` +Options: + +
+
-m, --machine
+
The name of the machine to use. Defaults to nitro-dev.
+
+ ### `update` Performs system updates (e.g. `sudo apt get update && sudo apt upgrade -y`). -This upgrades the `diesel` machine’s software packages to their newest versions: - -```bash -nitro update +```sh +nitro update [] ``` +Options: + +
+
-m, --machine
+
The name of the machine to use. Defaults to nitro-dev.
+
+ ### `version` Checks the currently version of nitro against the releases and shows any updated versions. -```bash +```sh nitro version ``` @@ -336,38 +517,51 @@ nitro version Configures Xdebug for remote access and debugging with PhpStorm or other IDEs. -Options: +```sh +nitro xdebug configure [] +``` -- `--php-version [argument]` install a specific version of PHP to enable for Xdebug +Options: -```bash -nitro xdebug configure --php-version 7.3 -``` +
+
-m, --machine
+
The name of the machine to use. Defaults to nitro-dev.
+
--php-version
+
The PHP version to configure Xdebug for
+
### `xdebug on` Enables Xdebug, which is installed and disabled by default on each machine. +```sh +nitro xdebug on [] +``` + Options: -- `--php-version [argument]` install a specific version of PHP to enable for Xdebug +
+
-m, --machine
+
The name of the machine to use. Defaults to nitro-dev.
+
--php-version
+
The PHP version to enable Xdebug for
+
This ensures Xdebug is installed for PHP 7.3 and enables it: -```bash -nitro xdebug on --php-version 7.3 -``` - ### `xdebug off` Disables Xdebug on a machine. -Options: - -- `--php-version [argument]` install a specific version of PHP to enable for Xdebug +```sh +nitro xdebug off [] +``` -This ensures Xdebug is installed for PHP 7.2 but disables it: +Options: -```bash -nitro xdebug off --php-version 7.2 -``` +
+
-m, --machine
+
The name of the machine to use. Defaults to nitro-dev.
+
--php-version
+
The PHP version to disable Xdebug for
+
diff --git a/UPGRADE.md b/UPGRADE.md new file mode 100644 index 00000000..a1b0befc --- /dev/null +++ b/UPGRADE.md @@ -0,0 +1,30 @@ +# Release Notes for Craft Nitro + +## Upgrading to Nitro 0.10.0 + +As of Nitro 0.10.0, all machine configs must be stored stored at `~/.nitro/.yaml`. + +If you are updating from a previous version, you will need to rename your current `~/.nitro/nitro.yaml` file based +on the name of your primary machine name. (If you can’t remember what that is, open up the file and check its `name` +value.) + +```sh +cd ~/.nitro +mv nitro.yaml .yaml +``` + +If your primary machine name was something besides `nitro-dev`, you will need to define a `NITRO_DEFAULT_MACHINE` +environment variable on your system, so Nitro knows which machine to work with by default. For example on macOS or +Unix/Linux systems, you can open your `~/.bash_profile` file (or `.zprofile` if using zsh) and add this to it: + +```bash +export NITRO_DEFAULT_MACHINE="" +``` + +Then paste the same line into your terminal, or restart it for the profile change to take effect. + +If you created any additional machines, you will need to move their configuration files over to `~/.nitro` as well: + +```sh +mv /path/to/project/nitro.yaml ~/.nitro/.yaml +``` diff --git a/buddy.yml b/buddy.yml index f79357fb..ec731013 100644 --- a/buddy.yml +++ b/buddy.yml @@ -1,5 +1,6 @@ - pipeline: "test" trigger_mode: "ON_EVERY_PUSH" + auto_clear_cache: true ref_name: "refs/heads/*" ref_type: "WILDCARD" trigger_condition: "ALWAYS" @@ -20,6 +21,7 @@ shell: "BASH" - pipeline: "release" trigger_mode: "ON_EVERY_PUSH" + auto_clear_cache: true ref_name: "refs/tags/*" ref_type: "WILDCARD" trigger_condition: "ALWAYS" diff --git a/config/config.go b/config/config.go index 7c71a153..b232ac3b 100644 --- a/config/config.go +++ b/config/config.go @@ -14,11 +14,10 @@ import ( ) type Config struct { - Name string `yaml:"name"` - PHP string `yaml:"php"` - CPUs string `yaml:"cpus"` - Disk string `yaml:"disk"` - Memory string `yaml:"memory"` + PHP string `yaml:"-"` + CPUs string `yaml:"-"` + Disk string `yaml:"-"` + Memory string `yaml:"-"` Mounts []Mount `yaml:"mounts,omitempty"` Databases []Database `yaml:"databases"` Sites []Site `yaml:"sites,omitempty"` @@ -171,6 +170,33 @@ func (c *Config) Save(filename string) error { return nil } +func (c *Config) SaveAs(home, machine string) error { + nitroDir := home + "/.nitro/" + + if err := helpers.MkdirIfNotExists(nitroDir); err != nil { + return err + } + filename := nitroDir + machine + ".yaml" + + _ = helpers.CreateFileIfNotExist(filename) + + f, err := os.OpenFile(filename, os.O_RDWR|os.O_CREATE, 0755) + if err != nil { + return err + } + + data, err := yaml.Marshal(c) + if err != nil { + return err + } + + if _, err := f.Write(data); err != nil { + return err + } + + return nil +} + func GetString(key, flag string) string { if viper.IsSet(key) && flag == "" { return viper.GetString(key) diff --git a/config/config_test.go b/config/config_test.go index c3e9ce73..da29b8d9 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -189,7 +189,6 @@ func TestConfig_RemoveSite(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { c := &Config{ - Name: tt.fields.Name, PHP: tt.fields.PHP, CPUs: tt.fields.CPUs, Disk: tt.fields.Disk, @@ -301,7 +300,6 @@ func TestConfig_AddMount(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { c := &Config{ - Name: tt.fields.Name, PHP: tt.fields.PHP, CPUs: tt.fields.CPUs, Disk: tt.fields.Disk, @@ -362,7 +360,6 @@ func TestConfig_AddSite(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { c := &Config{ - Name: tt.fields.Name, PHP: tt.fields.PHP, CPUs: tt.fields.CPUs, Disk: tt.fields.Disk, @@ -447,7 +444,6 @@ func TestConfig_RemoveMountBySiteWebroot(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { c := &Config{ - Name: tt.fields.Name, PHP: tt.fields.PHP, CPUs: tt.fields.CPUs, Disk: tt.fields.Disk, @@ -522,7 +518,6 @@ func TestConfig_RemoveSite1(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { c := &Config{ - Name: tt.fields.Name, PHP: tt.fields.PHP, CPUs: tt.fields.CPUs, Disk: tt.fields.Disk, diff --git a/config/testdata/configs/full-example.yaml b/config/testdata/configs/full-example.yaml index 94eb0b81..b68b12aa 100644 --- a/config/testdata/configs/full-example.yaml +++ b/config/testdata/configs/full-example.yaml @@ -1,8 +1,3 @@ -name: nitro-global -php: "7.4" -cpus: "1" -disk: 60G -memory: 4G mounts: - source: ~/go/src/github.com/craftcms/nitro/demo-site dest: /nitro/sites/demo-site diff --git a/config/testdata/configs/golden-full.yaml b/config/testdata/configs/golden-full.yaml index 4eb500fe..4c08b1ae 100644 --- a/config/testdata/configs/golden-full.yaml +++ b/config/testdata/configs/golden-full.yaml @@ -1,8 +1,3 @@ -name: nitro-global -php: "7.4" -cpus: "1" -disk: 60G -memory: 4G mounts: - source: ~/go/src/github.com/craftcms/nitro/production-site dest: /nitro/sites/production-site diff --git a/nitro.yaml b/examples/nitro-dev.yaml similarity index 66% rename from nitro.yaml rename to examples/nitro-dev.yaml index 2773cbac..a0c3200c 100644 --- a/nitro.yaml +++ b/examples/nitro-dev.yaml @@ -1,8 +1,3 @@ -name: nitro -php: "7.4" -cpus: "1" -disk: 40G -memory: 4G databases: - engine: mysql version: "5.7" diff --git a/examples/nitro-multiple-versions.yaml b/examples/nitro-multiple-versions.yaml index 48d7ba55..b1055691 100644 --- a/examples/nitro-multiple-versions.yaml +++ b/examples/nitro-multiple-versions.yaml @@ -1,8 +1,3 @@ -name: nitro -php: "7.4" -cpus: "1" -disk: 40G -memory: 4G databases: - engine: mysql version: "5.7" diff --git a/get.sh b/install.sh similarity index 78% rename from get.sh rename to install.sh index 4999f73b..aa544f5c 100755 --- a/get.sh +++ b/install.sh @@ -3,6 +3,7 @@ export GH_ORG=craftcms export SUCCESS_CMD="nitro" export BINLOCATION="/usr/local/bin" +export TEMP_FOLDER="temp_nitro_extract" version=$(curl -s https://api.github.com/repos/craftcms/nitro/releases/latest | grep -i tag_name | sed 's/\(\"tag_name\": \"\(.*\)\",\)/\2/' | tr -d '[:space:]') @@ -32,9 +33,10 @@ hasMultipass() { fi } -checkHash () { +checkHash() { sha_cmd="sha256sum" fileName=nitro_$2_checksums.txt + filePath=$(pwd)/$TEMP_FOLDER/$fileName checksumUrl=https://github.com/craftcms/nitro/releases/download/$version/$fileName targetFile=$3/$fileName @@ -45,61 +47,61 @@ checkHash () { if [ -x "$(command -v $shaCmd)" ]; then # download the checksum file. - (curl -sSL "$checksumUrl" --output "$targetFile") + (curl -sLS "$checksumUrl" --output "$targetFile") # Run the sha command against the zip and grab the hash from the first segment. zipHash="$($shaCmd $1 | cut -d' ' -f1 | tr -d '[:space:]')" # See if the has we calculated matches a result in the checksum file. - checkResultFileName=$(sed -n "s/^$zipHash //p" "$fileName") + checkResultFileName=$(sed -n "s/^$zipHash //p" "$filePath") # don't need this anymore - rm "nitro_$2_checksums.txt" + rm "$filePath" # Make sure the file names match up. if [ "$4" != "$checkResultFileName" ]; then - rm "$1"; + # rm "$1" echo "Checksums do not match. Exiting." exit 1 fi fi } -getNitro () { +getNitro() { uname=$(uname) userid=$(id -u) suffix="" case $uname in - "Darwin") - suffix="_darwin" - ;; + "Darwin") + suffix="_darwin" + ;; - "MINGW"*) - suffix=".exe" - BINLOCATION="$HOME/bin" - mkdir -p "$BINLOCATION" - ;; + "MINGW"*) + suffix=".exe" + BINLOCATION="$HOME/bin" + mkdir -p "$BINLOCATION" + ;; - "Linux") - arch=$(uname -m) - suffix="_linux" + "Linux") + arch=$(uname -m) + suffix="_linux" - case $arch in - "aarch64") - suffix="_linux" - ;; - esac + case $arch in + "aarch64") + suffix="_linux" + ;; + esac ;; esac - targetTempFolder="/tmp" - - if [ "$userid" != "0" ]; then - targetTempFolder="$(pwd)" + if [ ! -d $(pwd)/$TEMP_FOLDER ]; then + mkdir $(pwd)/$TEMP_FOLDER fi + targetTempFolder="$(pwd)/$TEMP_FOLDER" + fileName=nitro"$suffix"_x86_64.tar.gz packageUrl=https://github.com/craftcms/nitro/releases/download/$version/"$fileName" targetZipFile="$targetTempFolder"/$fileName @@ -111,14 +113,16 @@ getNitro () { if [ "$?" = "0" ]; then # unzip - tar xvzf "$targetZipFile" + tar xvzf "$targetZipFile" -C "$targetTempFolder" # verify checkHash "$targetZipFile" "$version" "$targetTempFolder" "$fileName" + mv "$targetTempFolder"/nitro ./nitro chmod +x ./nitro - echo "Download complete." + echo + echo "Download complete." if [ ! -w "$BINLOCATION" ]; then echo @@ -134,6 +138,7 @@ getNitro () { else echo echo "Running with sufficient permissions to attempt to move the nitro executable to $BINLOCATION" + echo if [ ! -w "$BINLOCATION/nitro" ] && [ -f "$BINLOCATION/nitro" ]; then echo @@ -149,20 +154,29 @@ getNitro () { mv ./nitro "$BINLOCATION"/nitro if [ "$?" = "0" ]; then - echo "nitro $version has been installed to $BINLOCATION" - echo + echo "Nitro $version has been installed to $BINLOCATION" fi - if [ -e "$targetZipFile" ]; then - rm "$targetZipFile" + if [ -e "$targetTempFolder" ]; then + rm -rf "$targetTempFolder" echo fi ${SUCCESS_CMD} + init fi fi } +init() { + echo + read -p "Initialize the primary machine now? " -n 1 -r + echo + if [[ $REPLY =~ ^[Yy]$ ]]; then + nitro init + fi +} + hasCurl hasMultipass -getNitro \ No newline at end of file +getNitro diff --git a/internal/cmd/add.go b/internal/cmd/add.go index 344577f1..02032ad9 100644 --- a/internal/cmd/add.go +++ b/internal/cmd/add.go @@ -1,11 +1,8 @@ package cmd import ( - "errors" "fmt" - "os" "os/exec" - "path/filepath" "github.com/manifoldco/promptui" "github.com/spf13/cobra" @@ -14,6 +11,8 @@ import ( "github.com/craftcms/nitro/config" "github.com/craftcms/nitro/internal/helpers" "github.com/craftcms/nitro/internal/nitro" + "github.com/craftcms/nitro/internal/sudo" + "github.com/craftcms/nitro/internal/webroot" "github.com/craftcms/nitro/validate" ) @@ -21,7 +20,7 @@ var addCommand = &cobra.Command{ Use: "add", Short: "Add site to machine", RunE: func(cmd *cobra.Command, args []string) error { - name := config.GetString("name", flagMachineName) + machine := flagMachineName // if there is no arg, get the current working dir // else get the first arg @@ -58,10 +57,10 @@ var addCommand = &cobra.Command{ // look for the www,public,public_html,www using the absolutePath variable // set the webrootName var (e.g. web) - var webroot string + var webrootDir string switch flagWebroot { case "": - foundDir, err := helpers.FindWebRoot(absolutePath) + foundDir, err := webroot.Find(absolutePath) if err != nil { return err } @@ -75,16 +74,16 @@ var addCommand = &cobra.Command{ } switch webrootEntered { case "": - webroot = foundDir + webrootDir = foundDir default: - webroot = webrootEntered + webrootDir = webrootEntered } default: - webroot = flagWebroot + webrootDir = flagWebroot } - // create the vmWebRootPath (e.g. "/nitro/sites/"+ directoryName + "/" | webrootName - webRootPath := fmt.Sprintf("/nitro/sites/%s/%s", directoryName, webroot) + // create the vmWebRootPath (e.g. "/nitro/sites/"+ hostName + "/" | webrootName + webRootPath := fmt.Sprintf("/nitro/sites/%s/%s", hostname, webrootDir) // load the config var configFile config.Config @@ -94,7 +93,7 @@ var addCommand = &cobra.Command{ // create a new mount // add the mount to configfile - mount := config.Mount{Source: absolutePath} + mount := config.Mount{Source: absolutePath, Dest: "/nitro/sites/" + hostname} if err := configFile.AddMount(mount); err != nil { return err } @@ -115,7 +114,7 @@ var addCommand = &cobra.Command{ fmt.Printf("%s has been added to nitro.yaml", hostname) applyPrompt := promptui.Prompt{ - Label: "Apply nitro.yaml changes now? [y]", + Label: "Apply changes now? [y]", } apply, err := applyPrompt.Run() @@ -135,33 +134,33 @@ var addCommand = &cobra.Command{ var actions []nitro.Action // mount the directory m := configFile.Mounts[len(configFile.Mounts)-1] - mountAction, err := nitro.MountDir(name, m.AbsSourcePath(), m.Dest) + mountAction, err := nitro.MountDir(machine, m.AbsSourcePath(), m.Dest) if err != nil { return err } actions = append(actions, *mountAction) // copy the nginx template - copyTemplateAction, err := nitro.CopyNginxTemplate(name, site.Hostname) + copyTemplateAction, err := nitro.CopyNginxTemplate(machine, site.Hostname) if err != nil { return err } actions = append(actions, *copyTemplateAction) // copy the nginx template - changeNginxVariablesAction, err := nitro.ChangeTemplateVariables(name, site.Webroot, site.Hostname, configFile.PHP, site.Aliases) + changeNginxVariablesAction, err := nitro.ChangeTemplateVariables(machine, site.Webroot, site.Hostname, configFile.PHP, site.Aliases) if err != nil { return err } actions = append(actions, *changeNginxVariablesAction...) - createSymlinkAction, err := nitro.CreateSiteSymllink(name, site.Hostname) + createSymlinkAction, err := nitro.CreateSiteSymllink(machine, site.Hostname) if err != nil { return err } actions = append(actions, *createSymlinkAction) - restartNginxAction, err := nitro.NginxReload(name) + restartNginxAction, err := nitro.NginxReload(machine) if err != nil { return err } @@ -179,31 +178,17 @@ var addCommand = &cobra.Command{ return err } - fmt.Println("Applied the changes and added", hostname, "to", name) + fmt.Println("Applied the changes and added", hostname, "to", machine) // prompt to add hosts file - cfgFile := viper.ConfigFileUsed() - if cfgFile == "" { - return errors.New("unable to find the config file") - } - - filePath, err := filepath.Abs(cfgFile) - if err != nil { - return err - } - nitro, err := exec.LookPath("nitro") if err != nil { return err } - fmt.Println("Modifying the hosts file to add sites for", config.GetString("name", ""), "(you will be prompted for your password)... ") - - hostsCmd := exec.Command("sudo", nitro, "-f", filePath, "hosts", "add") - hostsCmd.Stdout = os.Stdout - hostsCmd.Stderr = os.Stderr + fmt.Println("Adding", site.Hostname, "to your hosts file") - return hostsCmd.Run() + return sudo.RunCommand(nitro, machine, "hosts") }, } diff --git a/internal/cmd/apply.go b/internal/cmd/apply.go index 709963a8..4f88b5a8 100644 --- a/internal/cmd/apply.go +++ b/internal/cmd/apply.go @@ -9,28 +9,29 @@ import ( "github.com/spf13/viper" "github.com/craftcms/nitro/config" - "github.com/craftcms/nitro/internal/hack" + "github.com/craftcms/nitro/internal/diff" + "github.com/craftcms/nitro/internal/find" "github.com/craftcms/nitro/internal/nitro" ) var applyCommand = &cobra.Command{ Use: "apply", - Short: "Apply changes from nitro.yaml", - Hidden: true, + Short: "Apply changes from config", RunE: func(cmd *cobra.Command, args []string) error { - name := config.GetString("name", flagMachineName) + machine := flagMachineName + path, err := exec.LookPath("multipass") if err != nil { return err } - c := exec.Command(path, []string{"info", name, "--format=csv"}...) + c := exec.Command(path, []string{"info", machine, "--format=csv"}...) output, err := c.Output() if err != nil { return err } - attachedMounts, err := hack.FindMounts(name, output) + attachedMounts, err := find.Mounts(machine, output) if err != nil { return err } @@ -50,7 +51,7 @@ var applyCommand = &cobra.Command{ // find sites not created var sitesToCreate []config.Site for _, site := range configFile.Sites { - c := exec.Command(path, "exec", name, "--", "sudo", "bash", "/opt/nitro/scripts/site-exists.sh", site.Hostname) + c := exec.Command(path, "exec", machine, "--", "sudo", "bash", "/opt/nitro/scripts/site-exists.sh", site.Hostname) output, err := c.Output() if err != nil { return err @@ -60,12 +61,16 @@ var applyCommand = &cobra.Command{ } } - fmt.Printf("There are %d mounted directories and %d mounts in the config file. Applying changes now...\n", len(attachedMounts), len(fileMounts)) + // check for new dbs + dbsToCreate, err := find.ContainersToCreate(machine, configFile) + if err != nil { + return err + } // prompt? var actions []nitro.Action - mountActions, err := hack.MountDiffActions(name, attachedMounts, fileMounts) + mountActions, err := diff.MountActions(machine, attachedMounts, fileMounts) if err != nil { return err } @@ -73,27 +78,36 @@ var applyCommand = &cobra.Command{ // create site actions for _, site := range sitesToCreate { - m := configFile.FindMountBySiteWebroot(site.Webroot) - mountAction, err := nitro.MountDir(name, m.AbsSourcePath(), m.Dest) - if err != nil { - return err + // TODO abstract this logic into a func that takes mountActions and sites to return the mount action + for _, ma := range mountActions { + // break the string + mnt := strings.Split(ma.Args[2], ":") + + // if the webroot is not of the mounts, then we should create an action + if !strings.Contains(mnt[1], site.Webroot) { + m := configFile.FindMountBySiteWebroot(site.Webroot) + mountAction, err := nitro.MountDir(machine, m.AbsSourcePath(), m.Dest) + if err != nil { + return err + } + actions = append(actions, *mountAction) + } } - actions = append(actions, *mountAction) - copyTemplateAction, err := nitro.CopyNginxTemplate(name, site.Hostname) + copyTemplateAction, err := nitro.CopyNginxTemplate(machine, site.Hostname) if err != nil { return err } actions = append(actions, *copyTemplateAction) // copy the nginx template - changeNginxVariablesAction, err := nitro.ChangeTemplateVariables(name, site.Webroot, site.Hostname, configFile.PHP, site.Aliases) + changeNginxVariablesAction, err := nitro.ChangeTemplateVariables(machine, site.Webroot, site.Hostname, configFile.PHP, site.Aliases) if err != nil { return err } actions = append(actions, *changeNginxVariablesAction...) - createSymlinkAction, err := nitro.CreateSiteSymllink(name, site.Hostname) + createSymlinkAction, err := nitro.CreateSiteSymllink(machine, site.Hostname) if err != nil { return err } @@ -101,13 +115,28 @@ var applyCommand = &cobra.Command{ } if len(sitesToCreate) > 0 { - restartNginxAction, err := nitro.NginxReload(name) + restartNginxAction, err := nitro.NginxReload(machine) if err != nil { return err } actions = append(actions, *restartNginxAction) } + // create database actions + for _, database := range dbsToCreate { + volumeAction, err := nitro.CreateDatabaseVolume(machine, database.Engine, database.Version, database.Port) + if err != nil { + return err + } + actions = append(actions, *volumeAction) + + createDatabaseAction, err := nitro.CreateDatabaseContainer(machine, database.Engine, database.Version, database.Port) + if err != nil { + return err + } + actions = append(actions, *createDatabaseAction) + } + if flagDebug { for _, a := range actions { fmt.Println(a.Args) @@ -116,6 +145,9 @@ var applyCommand = &cobra.Command{ return nil } + fmt.Printf("There are %d mounted directories and %d mounts in the config file. Applying changes now...\n", len(attachedMounts), len(fileMounts)) + fmt.Printf("There are %d sites to create and %d sites in the config file. Applying changes now...\n", len(sitesToCreate), len(configFile.Sites)) + if err := nitro.Run(nitro.NewMultipassRunner("multipass"), actions); err != nil { return err } diff --git a/internal/cmd/machine_config.go b/internal/cmd/config.go similarity index 92% rename from internal/cmd/machine_config.go rename to internal/cmd/config.go index d234d7a3..f74c201c 100644 --- a/internal/cmd/machine_config.go +++ b/internal/cmd/config.go @@ -19,6 +19,13 @@ write_files: if test -f /etc/nginx/sites-enabled/"$site"; then echo "exists" fi + - path: /opt/nitro/scripts/docker-container-exists.sh + content: | + #!/usr/bin/env bash + NAME="$1" + if [ -n "$(docker ps -q -f name="$NAME")" ]; then + echo "exists" + fi - path: /opt/nitro/scripts/docker-exec-import.sh content: | #!/usr/bin/env bash diff --git a/internal/cmd/context.go b/internal/cmd/context.go index 21dc9b45..ef1d9961 100644 --- a/internal/cmd/context.go +++ b/internal/cmd/context.go @@ -24,7 +24,7 @@ var contextCommand = &cobra.Command{ return err } - fmt.Println("Using config file: ", configFile) + fmt.Println("Using config file:", configFile) fmt.Println("------") fmt.Print(string(data)) return nil diff --git a/internal/cmd/destroy.go b/internal/cmd/destroy.go new file mode 100644 index 00000000..5e406045 --- /dev/null +++ b/internal/cmd/destroy.go @@ -0,0 +1,60 @@ +package cmd + +import ( + "fmt" + "os/exec" + + "github.com/spf13/cobra" + "github.com/spf13/viper" + + "github.com/craftcms/nitro/config" + "github.com/craftcms/nitro/internal/nitro" + "github.com/craftcms/nitro/internal/sudo" +) + +var destroyCommand = &cobra.Command{ + Use: "destroy", + Short: "Destroy a machine", + RunE: func(cmd *cobra.Command, args []string) error { + machine := flagMachineName + + // get the sites + var cfg config.Config + if err := viper.Unmarshal(&cfg); err != nil { + return err + } + + var domains []string + for _, site := range cfg.Sites { + domains = append(domains, site.Hostname) + } + + destroyAction, err := nitro.Destroy(machine) + if err != nil { + return err + } + + if err := nitro.Run(nitro.NewMultipassRunner("multipass"), []nitro.Action{*destroyAction}); err != nil { + return err + } + + if len(domains) == 0 { + return nil + } + + cmds := []string{"hosts", "remove"} + for _, domain := range domains { + cmds = append(cmds, domain) + } + + // prompt to remove hosts file + nitro, err := exec.LookPath("nitro") + if err != nil { + return err + } + + fmt.Println("Removing sites from your hosts file") + + return sudo.RunCommand(nitro, machine, cmds...) + }, +} diff --git a/internal/cmd/edit.go b/internal/cmd/edit.go index 4ee209b9..33a14f3f 100644 --- a/internal/cmd/edit.go +++ b/internal/cmd/edit.go @@ -11,8 +11,8 @@ import ( ) var editCommand = &cobra.Command{ - Use: "edit", - Short: "Edit your nitro.yaml", + Use: "edit", + Short: "Edit your config", RunE: func(cmd *cobra.Command, args []string) error { cfgFile := viper.ConfigFileUsed() if cfgFile == "" { @@ -25,10 +25,7 @@ var editCommand = &cobra.Command{ } _, err = editor.CaptureInputFromEditor(filePath, editor.GetPreferredEditorFromEnvironment) - if err != nil { - return err - } - return nil + return err }, } diff --git a/internal/cmd/flags.go b/internal/cmd/flags.go index 8fb48894..6db87c15 100644 --- a/internal/cmd/flags.go +++ b/internal/cmd/flags.go @@ -1,7 +1,6 @@ package cmd var ( - flagConfigFile string flagMachineName string flagDebug bool flagCPUs int @@ -9,7 +8,6 @@ var ( flagDisk string flagPhpVersion string flagNginxLogsKind string - flagPermanent bool // flags for the add command flagHostname string diff --git a/internal/cmd/hosts.go b/internal/cmd/hosts.go index 6cc9fdab..3a286666 100644 --- a/internal/cmd/hosts.go +++ b/internal/cmd/hosts.go @@ -1,13 +1,68 @@ package cmd import ( + "errors" + "fmt" + "os" + "github.com/spf13/cobra" + "github.com/spf13/viper" + "github.com/txn2/txeh" + + "github.com/craftcms/nitro/config" + "github.com/craftcms/nitro/internal/hosts" + "github.com/craftcms/nitro/internal/nitro" ) var hostsCommand = &cobra.Command{ - Use: "hosts", - Short: "Manage your hosts file", + Use: "hosts", + Short: "Add sites to your hosts file", + Hidden: true, RunE: func(cmd *cobra.Command, args []string) error { - return cmd.Help() + machine := flagMachineName + + if !flagDebug { + uid := os.Geteuid() + if uid != 0 { + return errors.New("you do not appear to be running this command as root, so we cannot modify your hosts file") + } + } + + // get the requested machines ip + ip := nitro.IP(machine, nitro.NewMultipassRunner("multipass")) + + // get all of the sites from the config file + if !viper.IsSet("sites") { + return errors.New("unable to read sites from " + viper.ConfigFileUsed()) + } + + var sites []config.Site + if err := viper.UnmarshalKey("sites", &sites); err != nil { + return err + } + + var domains []string + for _, site := range sites { + domains = append(domains, site.Hostname) + } + + he, err := txeh.NewHostsDefault() + if err != nil { + return err + } + + if flagDebug { + for _, domain := range domains { + fmt.Println("adding", domain, "to hosts file") + } + + return nil + } + + return hosts.Add(he, ip, domains) }, } + +func init() { + hostsCommand.AddCommand(hostsRemoveCommand) +} diff --git a/internal/cmd/hosts_add.go b/internal/cmd/hosts_add.go deleted file mode 100644 index 1eabe318..00000000 --- a/internal/cmd/hosts_add.go +++ /dev/null @@ -1,55 +0,0 @@ -package cmd - -import ( - "errors" - "fmt" - "os" - - "github.com/spf13/cobra" - "github.com/spf13/viper" - "github.com/txn2/txeh" - - "github.com/craftcms/nitro/config" - "github.com/craftcms/nitro/internal/nitro" -) - -var hostsAddCommand = &cobra.Command{ - Use: "add", - Short: "Add an entry to your hosts file", - RunE: func(cmd *cobra.Command, args []string) error { - name := config.GetString("name", flagMachineName) - - uid := os.Geteuid() - if uid != 0 { - return errors.New("you do not appear to be running this command as root, so we cannot modify your hosts file") - } - - // get the requested machines ip - ip := nitro.IP(name, nitro.NewMultipassRunner("multipass")) - - // get all of the sites from the config file - if !viper.IsSet("sites") { - return errors.New("unable to read sites from " + viper.ConfigFileUsed()) - } - - var sites []config.Site - if err := viper.UnmarshalKey("sites", &sites); err != nil { - return err - } - - hosts, err := txeh.NewHostsDefault() - if err != nil { - return err - } - - var domains []string - for _, site := range sites { - domains = append(domains, site.Hostname) - fmt.Println("Adding", site.Hostname, "to", name) - } - - hosts.AddHosts(ip, domains) - - return hosts.Save() - }, -} diff --git a/internal/cmd/hosts_remove.go b/internal/cmd/hosts_remove.go index abce7a26..53de6604 100644 --- a/internal/cmd/hosts_remove.go +++ b/internal/cmd/hosts_remove.go @@ -2,48 +2,46 @@ package cmd import ( "errors" + "fmt" "os" "github.com/spf13/cobra" - "github.com/spf13/viper" "github.com/txn2/txeh" - "github.com/craftcms/nitro/config" + "github.com/craftcms/nitro/internal/hosts" ) var hostsRemoveCommand = &cobra.Command{ - Use: "remove", - Short: "Remove an entry from your hosts file", + Use: "remove", + Short: "Remove an entry from your hosts file", + Hidden: true, + Args: cobra.MinimumNArgs(1), RunE: func(cmd *cobra.Command, args []string) error { - _ = config.GetString("name", flagMachineName) - - uid := os.Geteuid() - if uid != 0 { - return errors.New("you do not appear to be running this command as root, so we cannot modify your hosts file") - } - - // get all of the sites from the config file - if !viper.IsSet("sites") { - return errors.New("unable to read sites from " + viper.ConfigFileUsed()) - } - - var sites []config.Site - if err := viper.UnmarshalKey("sites", &sites); err != nil { - return err + if !flagDebug { + uid := os.Geteuid() + if uid != 0 { + return errors.New("you do not appear to be running this command as root, so we cannot modify your hosts file") + } } - hosts, err := txeh.NewHostsDefault() + he, err := txeh.NewHostsDefault() if err != nil { return err } var domains []string - for _, site := range sites { - domains = append(domains, site.Hostname) + for _, site := range args { + domains = append(domains, site) } - hosts.RemoveHosts(domains) + if flagDebug { + for _, domain := range domains { + fmt.Println("removing", domain, "from hosts file") + } + + return nil + } - return hosts.Save() + return hosts.Remove(he, domains) }, } diff --git a/internal/cmd/hosts_view.go b/internal/cmd/hosts_view.go deleted file mode 100644 index e9a0c2eb..00000000 --- a/internal/cmd/hosts_view.go +++ /dev/null @@ -1,27 +0,0 @@ -package cmd - -import ( - "fmt" - - "github.com/spf13/cobra" - "github.com/txn2/txeh" - - "github.com/craftcms/nitro/config" -) - -var hostsShowCommand = &cobra.Command{ - Use: "view", - Short: "View your hosts file", - RunE: func(cmd *cobra.Command, args []string) error { - _ = config.GetString("name", flagMachineName) - - hosts, err := txeh.NewHostsDefault() - if err != nil { - return err - } - - fmt.Println(hosts.RenderHostsFile()) - - return nil - }, -} diff --git a/internal/cmd/import.go b/internal/cmd/import.go index ddd91d80..09d0fa9b 100644 --- a/internal/cmd/import.go +++ b/internal/cmd/import.go @@ -4,15 +4,17 @@ import ( "errors" "fmt" "os" - "path/filepath" "strings" "github.com/manifoldco/promptui" + "github.com/mitchellh/go-homedir" "github.com/spf13/cobra" "github.com/spf13/viper" "github.com/craftcms/nitro/config" + "github.com/craftcms/nitro/internal/helpers" "github.com/craftcms/nitro/internal/nitro" + "github.com/craftcms/nitro/internal/normalize" ) var importCommand = &cobra.Command{ @@ -20,21 +22,22 @@ var importCommand = &cobra.Command{ Short: "Import database into machine", Args: cobra.MinimumNArgs(1), RunE: func(cmd *cobra.Command, args []string) error { - name := config.GetString("name", flagMachineName) + machine := flagMachineName - // get the filename - path := strings.Split(args[0], string(os.PathSeparator)) - filename := path[len(path)-1] + home, err := homedir.Dir() + if err != nil { + return err + } - // get the abs path - fileAbsPath, err := filepath.Abs(filename) + // get the filename + filename, fileAbsPath, err := normalize.Path(args[0], home) if err != nil { return err } - // verify the file exists - if !fileExists(fileAbsPath) { - return errors.New(fmt.Sprintf("unable to located the file %q to import", args[0])) + // make sure the file exists + if !helpers.FileExists(fileAbsPath) { + return errors.New(fmt.Sprintf("Unable to locate the file %q", fileAbsPath)) } // which database engine? @@ -62,7 +65,7 @@ var importCommand = &cobra.Command{ transferAction := nitro.Action{ Type: "transfer", UseSyscall: false, - Args: []string{"transfer", fileAbsPath, name + ":" + filename}, + Args: []string{"transfer", fileAbsPath, machine + ":" + filename}, } actions = append(actions, transferAction) @@ -71,7 +74,7 @@ var importCommand = &cobra.Command{ engine = "postgres" } - importArgs := []string{"exec", name, "--", "bash", "/opt/nitro/scripts/docker-exec-import.sh", containerName, "nitro", filename, engine} + importArgs := []string{"exec", machine, "--", "bash", "/opt/nitro/scripts/docker-exec-import.sh", containerName, "nitro", filename, engine} dockerExecAction := nitro.Action{ Type: "exec", UseSyscall: false, diff --git a/internal/cmd/info.go b/internal/cmd/info.go index 78a51b95..f3dc3485 100644 --- a/internal/cmd/info.go +++ b/internal/cmd/info.go @@ -3,7 +3,6 @@ package cmd import ( "github.com/spf13/cobra" - "github.com/craftcms/nitro/config" "github.com/craftcms/nitro/internal/nitro" ) @@ -11,9 +10,9 @@ var infoCommand = &cobra.Command{ Use: "info", Short: "Show machine info", RunE: func(cmd *cobra.Command, args []string) error { - name := config.GetString("name", flagMachineName) + machine := flagMachineName - infoAction, err := nitro.Info(name) + infoAction, err := nitro.Info(machine) if err != nil { return err } diff --git a/internal/cmd/init.go b/internal/cmd/init.go new file mode 100644 index 00000000..39aa9e60 --- /dev/null +++ b/internal/cmd/init.go @@ -0,0 +1,225 @@ +package cmd + +import ( + "errors" + "fmt" + "strconv" + "strings" + + "github.com/mitchellh/go-homedir" + "github.com/spf13/cobra" + "github.com/spf13/viper" + + "github.com/craftcms/nitro/config" + "github.com/craftcms/nitro/internal/nitro" + "github.com/craftcms/nitro/internal/prompt" + "github.com/craftcms/nitro/validate" +) + +var initCommand = &cobra.Command{ + Use: "init", + Short: "Initialize a new machine", + RunE: func(cmd *cobra.Command, args []string) error { + machine := flagMachineName + + if viper.ConfigFileUsed() != "" { + // TODO prompt for the confirmation of re initing the machine + return errors.New("using a config file already") + } + + // we don't have a config file + // set the config file + var cfg config.Config + + // hardcode the CPUs until this issue is resolved + // https://github.com/craftcms/nitro/issues/65 + hardCodedCpus := "2" + cpuInt, err := strconv.Atoi(hardCodedCpus) + if err != nil { + return err + } + cfg.CPUs = hardCodedCpus + + // ask how much memory + memory, err := prompt.AskWithDefault("How much memory should we assign?", "4G", nil) + if err != nil { + return err + } + cfg.Memory = memory + + // how much disk space + disk, err := prompt.AskWithDefault("How much disk space should the machine have?", "40G", nil) + if err != nil { + return err + } + cfg.Disk = disk + + // which version of PHP + _, php := prompt.SelectWithDefault("Which version of PHP should we install?", "7.4", nitro.PHPVersions) + cfg.PHP = php + + // what database engine? + _, engine := prompt.SelectWithDefault("Which database engine should we setup?", "mysql", nitro.DBEngines) + + // which version should we use? + versions := nitro.DBVersions[engine] + defaultVersion := versions[0] + _, version := prompt.SelectWithDefault("Select a version of "+engine+" to use:", defaultVersion, versions) + + // get the port for the engine + port := "3306" + if strings.Contains(engine, "postgres") { + port = "5432" + } + // TODO check if the port has already been used and +1 it + + cfg.Databases = []config.Database{ + { + Engine: engine, + Version: version, + Port: port, + }, + } + + if err := validate.DatabaseConfig(cfg.Databases); err != nil { + return err + } + + // save the config file + home, err := homedir.Dir() + if err != nil { + return err + } + if err := cfg.SaveAs(home, machine); err != nil { + return err + } + + actions, err := createActions(machine, memory, disk, cpuInt, php, cfg.Databases, nil, nil) + if err != nil { + return err + } + + if flagDebug { + fmt.Println("---- COMMANDS ----") + for _, a := range actions { + fmt.Println(a.Args) + } + + return nil + } + + fmt.Println("Ok, applying the changes now") + + return nitro.Run(nitro.NewMultipassRunner("multipass"), actions) + }, +} + +func init() { + // initCommand.Flags().IntVar(&flagCPUs, "cpus", 0, "Number of CPUs to allocate") + initCommand.Flags().StringVar(&flagMemory, "memory", "", "Amount of memory to allocate") + initCommand.Flags().StringVar(&flagDisk, "disk", "", "Amount of disk space to allocate") + initCommand.Flags().StringVar(&flagPhpVersion, "php-version", "", "Which version of PHP to make default") +} + +func createActions(machine, memory, disk string, cpus int, phpVersion string, databases []config.Database, mounts []config.Mount, sites []config.Site) ([]nitro.Action, error) { + var actions []nitro.Action + launchAction, err := nitro.Launch(machine, cpus, memory, disk, CloudConfig) + if err != nil { + return nil, err + } + actions = append(actions, *launchAction) + + installAction, err := nitro.InstallPackages(machine, phpVersion) + if err != nil { + return nil, err + } + actions = append(actions, *installAction) + + // configure php settings that are specific to Craft + configurePhpMemoryAction, err := nitro.ConfigurePHPMemoryLimit(machine, phpVersion, "256M") + if err != nil { + return nil, err + } + actions = append(actions, *configurePhpMemoryAction) + + configureExecutionTimeAction, err := nitro.ConfigurePHPExecutionTimeLimit(machine, phpVersion, "240") + if err != nil { + return nil, err + } + actions = append(actions, *configureExecutionTimeAction) + + xdebugConfigureAction, err := nitro.ConfigureXdebug(machine, phpVersion) + if err != nil { + return nil, err + } + actions = append(actions, *xdebugConfigureAction) + + restartPhpFpmAction, err := nitro.RestartPhpFpm(machine, phpVersion) + if err != nil { + return nil, err + } + actions = append(actions, *restartPhpFpmAction) + + // if there are mounts, set them + for _, mount := range mounts { + mountDirAction, err := nitro.MountDir(machine, mount.AbsSourcePath(), mount.Dest) + if err != nil { + return nil, err + } + actions = append(actions, *mountDirAction) + } + + for _, database := range databases { + volumeAction, err := nitro.CreateDatabaseVolume(machine, database.Engine, database.Version, database.Port) + if err != nil { + return nil, err + } + actions = append(actions, *volumeAction) + + createDatabaseAction, err := nitro.CreateDatabaseContainer(machine, database.Engine, database.Version, database.Port) + if err != nil { + return nil, err + } + actions = append(actions, *createDatabaseAction) + } + + var siteErrs []error + + for _, site := range sites { + copyTemplateAction, err := nitro.CopyNginxTemplate(machine, site.Hostname) + if err != nil { + siteErrs = append(siteErrs, err) + continue + } + actions = append(actions, *copyTemplateAction) + + if site.Webroot == "" { + site.Webroot = "web" + } + + changeVarsActions, err := nitro.ChangeTemplateVariables(machine, site.Webroot, site.Hostname, phpVersion, site.Aliases) + if err != nil { + siteErrs = append(siteErrs, err) + continue + } + for _, a := range *changeVarsActions { + actions = append(actions, a) + } + + createSymlinkAction, err := nitro.CreateSiteSymllink(machine, site.Hostname) + if err != nil { + siteErrs = append(siteErrs, err) + continue + } + actions = append(actions, *createSymlinkAction) + + reloadNginxAction, err := nitro.NginxReload(machine) + if err != nil { + siteErrs = append(siteErrs, err) + continue + } + actions = append(actions, *reloadNginxAction) + } + + return actions, nil +} diff --git a/internal/cmd/ip.go b/internal/cmd/ip.go deleted file mode 100644 index 8a38f1dd..00000000 --- a/internal/cmd/ip.go +++ /dev/null @@ -1,30 +0,0 @@ -package cmd - -import ( - "errors" - "fmt" - - "github.com/spf13/cobra" - - "github.com/craftcms/nitro/config" - "github.com/craftcms/nitro/internal/nitro" -) - -var ipCommand = &cobra.Command{ - Use: "ip", - Short: "Show machine IP", - Hidden: true, - RunE: func(cmd *cobra.Command, args []string) error { - name := config.GetString("name", flagMachineName) - r := nitro.NewMultipassRunner("multipass") - - ip := nitro.IP(name, r) - if ip == "" { - return errors.New("could not get the IP of " + name) - } - - fmt.Println(ip) - - return nil - }, -} diff --git a/internal/cmd/logs.go b/internal/cmd/logs.go index dbf4f975..3368ceb1 100644 --- a/internal/cmd/logs.go +++ b/internal/cmd/logs.go @@ -17,7 +17,7 @@ var logsCommand = &cobra.Command{ Use: "logs", Short: "Show machine logs", RunE: func(cmd *cobra.Command, args []string) error { - name := config.GetString("name", flagMachineName) + machine := flagMachineName // define the flags opts := []string{"nginx", "database", "docker"} @@ -38,7 +38,7 @@ var logsCommand = &cobra.Command{ case "docker": validate := func(input string) error { if input == "" { - return errors.New("container name cannot be empty") + return errors.New("container machine cannot be empty") } if strings.Contains(input, " ") { return errors.New("container names cannot contain spaces") @@ -47,7 +47,7 @@ var logsCommand = &cobra.Command{ } containerNamePrompt := promptui.Prompt{ - Label: "Enter container name", + Label: "Enter container machine", Validate: validate, } @@ -56,7 +56,7 @@ var logsCommand = &cobra.Command{ return err } - dockerLogsAction, err := nitro.LogsDocker(name, containerName) + dockerLogsAction, err := nitro.LogsDocker(machine, containerName) if err != nil { return err } @@ -80,7 +80,7 @@ var logsCommand = &cobra.Command{ if err != nil { return err } - dockerLogsAction, err := nitro.LogsDocker(name, containerName) + dockerLogsAction, err := nitro.LogsDocker(machine, containerName) if err != nil { return err } @@ -88,7 +88,7 @@ var logsCommand = &cobra.Command{ fmt.Println("Here are the database logs for", containerName, "...") default: fmt.Println("Here are the nginx logs...") - nginxLogsAction, err := nitro.LogsNginx(name, flagNginxLogsKind) + nginxLogsAction, err := nitro.LogsNginx(machine, flagNginxLogsKind) if err != nil { return err } diff --git a/internal/cmd/machine.go b/internal/cmd/machine.go deleted file mode 100644 index 0ef55746..00000000 --- a/internal/cmd/machine.go +++ /dev/null @@ -1,13 +0,0 @@ -package cmd - -import ( - "github.com/spf13/cobra" -) - -var machineCommand = &cobra.Command{ - Use: "machine", - Short: "Manage Nitro machines", - RunE: func(cmd *cobra.Command, args []string) error { - return cmd.Help() - }, -} diff --git a/internal/cmd/machine_create.go b/internal/cmd/machine_create.go deleted file mode 100644 index d60aee30..00000000 --- a/internal/cmd/machine_create.go +++ /dev/null @@ -1,320 +0,0 @@ -package cmd - -import ( - "errors" - "fmt" - "strconv" - - "github.com/manifoldco/promptui" - "github.com/mitchellh/go-homedir" - "github.com/spf13/cobra" - "github.com/spf13/viper" - - "github.com/craftcms/nitro/config" - "github.com/craftcms/nitro/internal/helpers" - "github.com/craftcms/nitro/internal/nitro" - "github.com/craftcms/nitro/internal/prompt" - "github.com/craftcms/nitro/validate" -) - -var createCommand = &cobra.Command{ - Use: "create", - Aliases: []string{"bootstrap", "boot"}, - Short: "Create a machine", - Example: "nitro machine create --name example-machine --cpus 4 --memory 4G --disk 60G --php-version 7.4", - PreRun: func(cmd *cobra.Command, args []string) { - if viper.ConfigFileUsed() != "" { - fmt.Println("Using config file:", viper.ConfigFileUsed()) - } - }, - RunE: func(cmd *cobra.Command, args []string) error { - // load the config file - // if the config does not exist, prompt the user - if viper.ConfigFileUsed() == "" { - home, err := homedir.Dir() - if err != nil { - return err - } - - // make the ~/.nitro/ directory - nitroDir := home + "/.nitro/" - if err := helpers.MkdirIfNotExists(nitroDir); err != nil { - return err - } - - if err := helpers.CreateFileIfNotExist(nitroDir + "nitro.yaml"); err != nil { - fmt.Println(err) - } - - // set the config file - var configFile config.Config - - // name - name, err := prompt.Ask("What should the machine be named?", "nitro-dev", validate.MachineName) - if err != nil { - return err - } - configFile.Name = name - - // number of cpus 1 - cpus, err := prompt.Ask("How many CPUs should the machine have?", "1", nil) - if err != nil { - return err - } - configFile.CPUs = cpus - - // how much memory 4G - memory, err := prompt.Ask("How much memory should the machine have?", "4G", validate.Memory) - if err != nil { - return err - } - configFile.Memory = memory - - // how large should the disk size be? 40G - disk, err := prompt.Ask("How much disk space should the machine have?", "40G", validate.Memory) - if err != nil { - return err - } - configFile.Disk = disk - - // which version of PHP would you like installed? 7.5 - phpPrompt := promptui.Select{ - Label: "Which version of PHP should we install?", - Items: nitro.PHPVersions, - CursorPos: 0, - } - _, phpVersion, err := phpPrompt.Run() - if err != nil { - return err - } - configFile.PHP = phpVersion - - // what database engine would you like to use? mysql - dbEnginePrompt := promptui.Select{ - Label: "Which database engine should the machine have?", - Items: nitro.DBEngines, - CursorPos: 0, - } - _, dbEngine, err := dbEnginePrompt.Run() - if err != nil { - return err - } - - _, dbVersion := prompt.Select("Select a version of "+dbEngine+" to use:", nitro.DBVersions[dbEngine]) - - dbPort := "3306" - if dbEngine == "postgres" { - dbPort = "5432" - } - - db := config.Database{ - Engine: dbEngine, - Version: dbVersion, - Port: dbPort, - } - configFile.Databases = []config.Database{db} - - if err := validate.DatabaseConfig(configFile.Databases); err != nil { - return err - } - - // save the config file - if err := configFile.Save(nitroDir + "nitro.yaml"); err != nil { - return err - } - - cpu, err := strconv.Atoi(cpus) - if err != nil { - return err - } - - actions, err := createActions(name, memory, disk, cpu, phpVersion, configFile.Databases, nil, nil) - if err != nil { - return err - } - - if flagDebug { - fmt.Println("---- COMMANDS ----") - for _, a := range actions { - fmt.Println(a.Args) - } - - return nil - } - - return nitro.Run(nitro.NewMultipassRunner("multipass"), actions) - } - - // run the actions - - // grab the config/options for the command - name := config.GetString("name", flagMachineName) - cpus := config.GetInt("cpus", flagCPUs) - memory := config.GetString("memory", flagMemory) - disk := config.GetString("disk", flagDisk) - phpVersion := config.GetString("php", flagPhpVersion) - - // validate options - if err := validate.DiskSize(disk); err != nil { - return err - } - if err := validate.Memory(memory); err != nil { - return err - } - if err := validate.PHPVersion(phpVersion); err != nil { - return err - } - if !viper.IsSet("databases") { - return errors.New("no databases defined in " + viper.ConfigFileUsed()) - } - - var databases []config.Database - if err := viper.UnmarshalKey("databases", &databases); err != nil { - return err - } - - if err := validate.DatabaseConfig(databases); err != nil { - return err - } - - var mounts []config.Mount - if viper.IsSet("mounts") { - if err := viper.UnmarshalKey("mounts", &mounts); err != nil { - return err - } - } - - var sites []config.Site - if viper.IsSet("sites") { - if err := viper.UnmarshalKey("sites", &sites); err != nil { - return err - } - } - - actions, err := createActions(name, memory, disk, cpus, phpVersion, databases, mounts, sites) - if err != nil { - return err - } - - if flagDebug { - fmt.Println("---- COMMANDS ----") - for _, a := range actions { - fmt.Println(a.Args) - } - - return nil - } - - return nitro.Run(nitro.NewMultipassRunner("multipass"), actions) - }, -} - -func init() { - createCommand.Flags().IntVar(&flagCPUs, "cpus", 0, "Number of CPUs to allocate") - createCommand.Flags().StringVar(&flagMemory, "memory", "", "Amount of memory to allocate") - createCommand.Flags().StringVar(&flagDisk, "disk", "", "Amount of disk space to allocate") - createCommand.Flags().StringVar(&flagPhpVersion, "php-version", "", "Which version of PHP to make default") -} - -func createActions(name, memory, disk string, cpus int, phpVersion string, databases []config.Database, mounts []config.Mount, sites []config.Site) ([]nitro.Action, error) { - var actions []nitro.Action - launchAction, err := nitro.Launch(name, cpus, memory, disk, CloudConfig) - if err != nil { - return nil, err - } - actions = append(actions, *launchAction) - - installAction, err := nitro.InstallPackages(name, phpVersion) - if err != nil { - return nil, err - } - actions = append(actions, *installAction) - - // configure php settings that are specific to Craft - configurePhpMemoryAction, err := nitro.ConfigurePHPMemoryLimit(name, phpVersion, "256M") - if err != nil { - return nil, err - } - actions = append(actions, *configurePhpMemoryAction) - - configureExecutionTimeAction, err := nitro.ConfigurePHPExecutionTimeLimit(name, phpVersion, "240") - if err != nil { - return nil, err - } - actions = append(actions, *configureExecutionTimeAction) - - xdebugConfigureAction, err := nitro.ConfigureXdebug(name, phpVersion) - if err != nil { - return nil, err - } - actions = append(actions, *xdebugConfigureAction) - - restartPhpFpmAction, err := nitro.RestartPhpFpm(name, phpVersion) - if err != nil { - return nil, err - } - actions = append(actions, *restartPhpFpmAction) - - // if there are mounts, set them - for _, mount := range mounts { - mountDirAction, err := nitro.MountDir(name, mount.AbsSourcePath(), mount.Dest) - if err != nil { - return nil, err - } - actions = append(actions, *mountDirAction) - } - - for _, database := range databases { - volumeAction, err := nitro.CreateDatabaseVolume(name, database.Engine, database.Version, database.Port) - if err != nil { - return nil, err - } - actions = append(actions, *volumeAction) - - createDatabaseAction, err := nitro.CreateDatabaseContainer(name, database.Engine, database.Version, database.Port) - if err != nil { - return nil, err - } - actions = append(actions, *createDatabaseAction) - } - - var siteErrs []error - - for _, site := range sites { - copyTemplateAction, err := nitro.CopyNginxTemplate(name, site.Hostname) - if err != nil { - siteErrs = append(siteErrs, err) - continue - } - actions = append(actions, *copyTemplateAction) - - if site.Webroot == "" { - site.Webroot = "web" - } - - changeVarsActions, err := nitro.ChangeTemplateVariables(name, site.Webroot, site.Hostname, phpVersion, site.Aliases) - if err != nil { - siteErrs = append(siteErrs, err) - continue - } - for _, a := range *changeVarsActions { - actions = append(actions, a) - } - - createSymlinkAction, err := nitro.CreateSiteSymllink(name, site.Hostname) - if err != nil { - siteErrs = append(siteErrs, err) - continue - } - actions = append(actions, *createSymlinkAction) - - reloadNginxAction, err := nitro.NginxReload(name) - if err != nil { - siteErrs = append(siteErrs, err) - continue - } - actions = append(actions, *reloadNginxAction) - } - - return actions, nil -} diff --git a/internal/cmd/machine_destroy.go b/internal/cmd/machine_destroy.go deleted file mode 100644 index 0803fd81..00000000 --- a/internal/cmd/machine_destroy.go +++ /dev/null @@ -1,35 +0,0 @@ -package cmd - -import ( - "fmt" - - "github.com/spf13/cobra" - - "github.com/craftcms/nitro/config" - "github.com/craftcms/nitro/internal/nitro" -) - -var destroyCommand = &cobra.Command{ - Use: "destroy", - Short: "Destroy a machine", - RunE: func(cmd *cobra.Command, args []string) error { - name := config.GetString("name", flagMachineName) - - if flagPermanent { - fmt.Println("Permanently destroying", name) - } else { - fmt.Println("Gently destroying", name) - } - - destroyAction, err := nitro.Destroy(name, flagPermanent) - if err != nil { - return err - } - - return nitro.Run(nitro.NewMultipassRunner("multipass"), []nitro.Action{*destroyAction}) - }, -} - -func init() { - destroyCommand.Flags().BoolVarP(&flagPermanent, "permanent", "p", false, "Permanently destroy the machine") -} diff --git a/internal/cmd/redis.go b/internal/cmd/redis.go index e37a6e9f..0bd90195 100644 --- a/internal/cmd/redis.go +++ b/internal/cmd/redis.go @@ -3,7 +3,6 @@ package cmd import ( "github.com/spf13/cobra" - "github.com/craftcms/nitro/config" "github.com/craftcms/nitro/internal/nitro" ) @@ -11,9 +10,9 @@ var redisCommand = &cobra.Command{ Use: "redis", Short: "Enter a redis shell", RunE: func(cmd *cobra.Command, args []string) error { - name := config.GetString("name", flagMachineName) + machine := flagMachineName - redisAction, err := nitro.Redis(name) + redisAction, err := nitro.Redis(machine) if err != nil { return err } diff --git a/internal/cmd/remove.go b/internal/cmd/remove.go index cb4fdb35..b32c0970 100644 --- a/internal/cmd/remove.go +++ b/internal/cmd/remove.go @@ -4,6 +4,7 @@ import ( "bytes" "errors" "fmt" + "os/exec" "github.com/spf13/cobra" "github.com/spf13/viper" @@ -12,19 +13,26 @@ import ( "github.com/craftcms/nitro/config" "github.com/craftcms/nitro/internal/nitro" "github.com/craftcms/nitro/internal/prompt" + "github.com/craftcms/nitro/internal/sudo" ) var removeCommand = &cobra.Command{ Use: "remove", Short: "Manage your nitro sites", RunE: func(cmd *cobra.Command, args []string) error { + machine := flagMachineName + var configFile config.Config if err := viper.Unmarshal(&configFile); err != nil { return err } - name := configFile.Name sites := configFile.GetSites() + + if len(sites) == 0 { + return errors.New("there are no sites to remove") + } + i, _ := prompt.Select("Select site to remove", configFile.SitesAsList()) site := sites[i] @@ -59,13 +67,16 @@ var removeCommand = &cobra.Command{ if err := viper.Unmarshal(&messyConfig); err != nil { return err } - // save that config in the right order - if err := messyConfig.Save(viper.ConfigFileUsed()); err != nil { - return err + + if !flagDebug { + // save that config in the right order + if err := messyConfig.Save(viper.ConfigFileUsed()); err != nil { + return err + } } // END HACK - actions, err := removeActions(name, *mount, site) + actions, err := removeActions(machine, *mount, site) if err != nil { return err } @@ -83,9 +94,17 @@ var removeCommand = &cobra.Command{ return err } - fmt.Println("Removed the site from your nitro.yaml and applied the changes.") + fmt.Println("Removed the site from your config and applied the changes.") + + // prompt to remove hosts file + nitro, err := exec.LookPath("nitro") + if err != nil { + return err + } + + fmt.Println("Removing site from your hosts file") - return nil + return sudo.RunCommand(nitro, machine, "hosts", "remove", site.Hostname) }, } diff --git a/internal/cmd/machine_restart.go b/internal/cmd/restart.go similarity index 51% rename from internal/cmd/machine_restart.go rename to internal/cmd/restart.go index 7256e4e9..dac75d73 100644 --- a/internal/cmd/machine_restart.go +++ b/internal/cmd/restart.go @@ -1,9 +1,10 @@ package cmd import ( + "fmt" + "github.com/spf13/cobra" - "github.com/craftcms/nitro/config" "github.com/craftcms/nitro/internal/nitro" ) @@ -11,13 +12,19 @@ var restartCommand = &cobra.Command{ Use: "restart", Short: "Restart a machine", RunE: func(cmd *cobra.Command, args []string) error { - name := config.GetString("name", flagMachineName) + machine := flagMachineName - restartAction, err := nitro.Restart(name) + restartAction, err := nitro.Restart(machine) if err != nil { return err } - return nitro.Run(nitro.NewMultipassRunner("multipass"), []nitro.Action{*restartAction}) + if err := nitro.Run(nitro.NewMultipassRunner("multipass"), []nitro.Action{*restartAction}); err != nil { + return err + } + + fmt.Println("Restarted", machine) + + return nil }, } diff --git a/internal/cmd/root.go b/internal/cmd/root.go index 33d4f277..e7974eb7 100644 --- a/internal/cmd/root.go +++ b/internal/cmd/root.go @@ -21,10 +21,10 @@ func init() { // set persistent flags on the root command rootCmd.PersistentFlags().StringVarP(&flagMachineName, "machine", "m", "", "Name of the machine.") rootCmd.PersistentFlags().BoolVarP(&flagDebug, "debug", "d", false, "Bypass executing the commands.") - rootCmd.PersistentFlags().StringVarP(&flagConfigFile, "config", "f", "", "Configuration file to use.") // add commands to root rootCmd.AddCommand( + initCommand, addCommand, sshCommand, updateCommand, @@ -32,21 +32,19 @@ func init() { stopCommand, restartCommand, startCommand, - machineCommand, logsCommand, xdebugCommand, redisCommand, - hostsCommand, contextCommand, selfUpdateCommand, applyCommand, removeCommand, + destroyCommand, editCommand, importCommand, + hostsCommand, ) xdebugCommand.AddCommand(xdebugOnCommand, xdebugOffCommand, xdebugConfigureCommand) - machineCommand.AddCommand(destroyCommand, createCommand, restartCommand, startCommand, stopCommand) - hostsCommand.AddCommand(hostsAddCommand, hostsRemoveCommand, hostsShowCommand) } func Execute() { @@ -56,14 +54,21 @@ func Execute() { } func loadConfig() { - if flagConfigFile != "" { - viper.SetConfigFile(flagConfigFile) - } else { - home, _ := homedir.Dir() + home, _ := homedir.Dir() - viper.AddConfigPath(home + "/" + ".nitro") - viper.SetConfigName("nitro") - viper.SetConfigType("yaml") + viper.AddConfigPath(home + "/" + ".nitro") + viper.SetConfigType("yaml") + + defaultMachine := os.Getenv("NITRO_DEFAULT_MACHINE") + + if flagMachineName != "" { + viper.SetConfigName(flagMachineName) + } else if defaultMachine != "" { + flagMachineName = defaultMachine + viper.SetConfigName(defaultMachine) + } else { + flagMachineName = "nitro-dev" + viper.SetConfigName("nitro-dev") } _ = viper.ReadInConfig() diff --git a/internal/cmd/self-update.go b/internal/cmd/self-update.go index d7620d79..f31ca2c1 100644 --- a/internal/cmd/self-update.go +++ b/internal/cmd/self-update.go @@ -17,17 +17,11 @@ var selfUpdateCommand = &cobra.Command{ Use: "self-update", Short: "Update Nitro to the latest", RunE: func(cmd *cobra.Command, args []string) error { - // TODO - // call public github api - // if latest version is not current version (does not match) - // then download get.sh - - fileUrl := "https://raw.githubusercontent.com/craftcms/nitro/master/get.sh" + fileUrl := "https://raw.githubusercontent.com/craftcms/nitro/master/install.sh" tempFolder := os.TempDir() - defer os.Remove(tempFolder) - localFile := filepath.Join(tempFolder, "get.sh") + localFile := filepath.Join(tempFolder, "install.sh") if err := DownloadFile(localFile, fileUrl); err != nil { return err @@ -48,7 +42,7 @@ var selfUpdateCommand = &cobra.Command{ fmt.Println(v) } - return nil + return os.Remove(tempFolder) }, } diff --git a/internal/cmd/site_remove.go b/internal/cmd/site_remove.go deleted file mode 100644 index c90e8dc7..00000000 --- a/internal/cmd/site_remove.go +++ /dev/null @@ -1,100 +0,0 @@ -package cmd - -import ( - "errors" - "fmt" - - "github.com/manifoldco/promptui" - "github.com/spf13/cobra" - "github.com/spf13/viper" - - "github.com/craftcms/nitro/config" - "github.com/craftcms/nitro/internal/nitro" -) - -var siteRemoveCommand = &cobra.Command{ - Use: "remove", - Short: "Remove a site from a machine", - Long: `Removing a site will perform the following steps: - -1. Remove the virtual host from nginx sites enabled -2. Delete the directory in "app/sites/xmydomain.test" -3. Unmount the local directory from the machine -4. Restart the nginx web server -5. Remove the site from your nitro.yaml sites configuration -`, - RunE: func(cmd *cobra.Command, args []string) error { - name := config.GetString("name", flagMachineName) - // make sure we have sites in the configFile - if !viper.IsSet("sites") { - return errors.New("no sites found in " + viper.ConfigFileUsed()) - } - - // create the prompt - var configFile config.Config - if err := viper.Unmarshal(&configFile); err != nil { - return err - } - - var sites []string - for _, site := range configFile.Sites { - sites = append(sites, site.Hostname) - } - - prompt := promptui.Select{ - Label: "Select site to remove", - Items: sites, - } - _, site, err := prompt.Run() - if err != nil { - return err - } - - var actions []nitro.Action - - // remove symlink - removeSymlinkAction, err := nitro.RemoveSymlink(name, site) - if err != nil { - return err - } - actions = append(actions, *removeSymlinkAction) - - // remove mount - unmountAction, err := nitro.Unmount(name, site) - if err != nil { - return err - } - actions = append(actions, *unmountAction) - - // remove the directory - removeNginxSiteDirectoryAction, err := nitro.RemoveNginxSiteDirectory(name, site) - if err != nil { - return err - } - actions = append(actions, *removeNginxSiteDirectoryAction) - - // restart nginx - restartNginxAction, err := nitro.NginxReload(name) - if err != nil { - return err - } - actions = append(actions, *restartNginxAction) - - // remove from configFile file - if err := configFile.RemoveSite(site); err != nil { - return err - } - - if err := nitro.Run(nitro.NewMultipassRunner("multipass"), actions); err != nil { - return nil - } - - if err := configFile.Save(viper.ConfigFileUsed()); err != nil { - return err - } - - fmt.Printf("Removed %q from %q", site, name) - - return nil - }, -} diff --git a/internal/cmd/ssh.go b/internal/cmd/ssh.go index f78f0231..0f451c28 100644 --- a/internal/cmd/ssh.go +++ b/internal/cmd/ssh.go @@ -3,7 +3,6 @@ package cmd import ( "github.com/spf13/cobra" - "github.com/craftcms/nitro/config" "github.com/craftcms/nitro/internal/nitro" ) @@ -11,9 +10,9 @@ var sshCommand = &cobra.Command{ Use: "ssh", Short: "SSH into a machine", RunE: func(cmd *cobra.Command, args []string) error { - name := config.GetString("name", flagMachineName) + machine := flagMachineName - sshAction, err := nitro.SSH(name) + sshAction, err := nitro.SSH(machine) if err != nil { return err } diff --git a/internal/cmd/machine_start.go b/internal/cmd/start.go similarity index 54% rename from internal/cmd/machine_start.go rename to internal/cmd/start.go index a9002221..d2737770 100644 --- a/internal/cmd/machine_start.go +++ b/internal/cmd/start.go @@ -1,9 +1,10 @@ package cmd import ( + "fmt" + "github.com/spf13/cobra" - "github.com/craftcms/nitro/config" "github.com/craftcms/nitro/internal/nitro" ) @@ -12,13 +13,19 @@ var startCommand = &cobra.Command{ Aliases: []string{"up"}, Short: "Start a machine", RunE: func(cmd *cobra.Command, args []string) error { - name := config.GetString("name", flagMachineName) + machine := flagMachineName - startAction, err := nitro.Start(name) + startAction, err := nitro.Start(machine) if err != nil { return err } - return nitro.Run(nitro.NewMultipassRunner("multipass"), []nitro.Action{*startAction}) + if err := nitro.Run(nitro.NewMultipassRunner("multipass"), []nitro.Action{*startAction}); err != nil { + return err + } + + fmt.Println("Started", machine) + + return nil }, } diff --git a/internal/cmd/machine_stop.go b/internal/cmd/stop.go similarity index 52% rename from internal/cmd/machine_stop.go rename to internal/cmd/stop.go index fff6a6a0..6066cacc 100644 --- a/internal/cmd/machine_stop.go +++ b/internal/cmd/stop.go @@ -1,9 +1,10 @@ package cmd import ( + "fmt" + "github.com/spf13/cobra" - "github.com/craftcms/nitro/config" "github.com/craftcms/nitro/internal/nitro" ) @@ -11,13 +12,19 @@ var stopCommand = &cobra.Command{ Use: "stop", Short: "Stop a machine", RunE: func(cmd *cobra.Command, args []string) error { - name := config.GetString("machine", flagMachineName) + machine := flagMachineName - stopAction, err := nitro.Stop(name) + stopAction, err := nitro.Stop(machine) if err != nil { return err } - return nitro.Run(nitro.NewMultipassRunner("multipass"), []nitro.Action{*stopAction}) + if err := nitro.Run(nitro.NewMultipassRunner("multipass"), []nitro.Action{*stopAction}); err != nil { + return err + } + + fmt.Println("Stopped", machine) + + return nil }, } diff --git a/internal/cmd/machine_update.go b/internal/cmd/update.go similarity index 62% rename from internal/cmd/machine_update.go rename to internal/cmd/update.go index a427f65f..67fdc816 100644 --- a/internal/cmd/machine_update.go +++ b/internal/cmd/update.go @@ -1,9 +1,10 @@ package cmd import ( + "fmt" + "github.com/spf13/cobra" - "github.com/craftcms/nitro/config" "github.com/craftcms/nitro/internal/nitro" ) @@ -12,21 +13,27 @@ var updateCommand = &cobra.Command{ Aliases: []string{"upgrade"}, Short: "Update a machine", RunE: func(cmd *cobra.Command, args []string) error { - name := config.GetString("name", flagMachineName) + machine := flagMachineName var actions []nitro.Action - updateAction, err := nitro.Update(name) + updateAction, err := nitro.Update(machine) if err != nil { return err } actions = append(actions, *updateAction) - upgradeAction, err := nitro.Upgrade(name) + upgradeAction, err := nitro.Upgrade(machine) if err != nil { return err } actions = append(actions, *upgradeAction) - return nitro.Run(nitro.NewMultipassRunner("multipass"), actions) + if err := nitro.Run(nitro.NewMultipassRunner("multipass"), actions); err != nil { + return err + } + + fmt.Println("Updated", machine) + + return nil }, } diff --git a/internal/cmd/version.go b/internal/cmd/version.go index d1b856cb..ee65c108 100644 --- a/internal/cmd/version.go +++ b/internal/cmd/version.go @@ -6,7 +6,7 @@ import ( "github.com/spf13/cobra" - "github.com/craftcms/nitro/internal/hack" + "github.com/craftcms/nitro/internal/version" ) var ( @@ -20,7 +20,7 @@ var ( fmt.Printf("nitro %s\n", Version) fmt.Println("") - latest, err := hack.GetLatestVersion(http.DefaultClient, "https://api.github.com/repos/craftcms/nitro/releases") + latest, err := version.GetLatest(http.DefaultClient, "https://api.github.com/repos/craftcms/nitro/releases") if err != nil { return err } diff --git a/internal/cmd/x.go b/internal/cmd/x.go deleted file mode 100644 index f2b62ad8..00000000 --- a/internal/cmd/x.go +++ /dev/null @@ -1,46 +0,0 @@ -package cmd - -import ( - "errors" - "fmt" - "os" - "os/exec" - "path/filepath" - - "github.com/spf13/cobra" - "github.com/spf13/viper" - - "github.com/craftcms/nitro/config" -) - -var xCommand = &cobra.Command{ - Use: "x", - Hidden: true, - RunE: func(cmd *cobra.Command, args []string) error { - cfgFile := viper.ConfigFileUsed() - if cfgFile == "" { - return errors.New("unable to find the config file") - } - - filePath, err := filepath.Abs(cfgFile) - if err != nil { - return err - } - - nitro, err := exec.LookPath("nitro") - if err != nil { - return err - } - - fmt.Println("Modifying the hosts file to add sites for", config.GetString("name", ""), "(you will be prompted for your password)... ") - hostsCmd := exec.Command("sudo", nitro, "-f", filePath, "hosts", "add") - hostsCmd.Stdout = os.Stdout - hostsCmd.Stderr = os.Stderr - - return hostsCmd.Run() - }, -} - -func init() { - rootCmd.AddCommand(xCommand) -} diff --git a/internal/cmd/xdebug_configure.go b/internal/cmd/xdebug_configure.go index bd861f75..745d596e 100644 --- a/internal/cmd/xdebug_configure.go +++ b/internal/cmd/xdebug_configure.go @@ -8,20 +8,22 @@ import ( ) var xdebugConfigureCommand = &cobra.Command{ - Use: "configure", - Short: "Configure Xdebug on a machine", + Use: "configure", + Short: "Configure Xdebug on a machine", + Hidden: true, RunE: func(cmd *cobra.Command, args []string) error { - name := config.GetString("name", flagMachineName) + machine := flagMachineName + php := config.GetString("php", flagPhpVersion) var actions []nitro.Action - xdebugConfigureAction, err := nitro.ConfigureXdebug(name, php) + xdebugConfigureAction, err := nitro.ConfigureXdebug(machine, php) if err != nil { return err } actions = append(actions, *xdebugConfigureAction) - restartPhpFpmAction, err := nitro.RestartPhpFpm(name, php) + restartPhpFpmAction, err := nitro.RestartPhpFpm(machine, php) if err != nil { return err } diff --git a/internal/cmd/xdebug_off.go b/internal/cmd/xdebug_off.go index 6a781cb0..53743a90 100644 --- a/internal/cmd/xdebug_off.go +++ b/internal/cmd/xdebug_off.go @@ -12,17 +12,26 @@ var xdebugOffCommand = &cobra.Command{ Use: "off", Short: "Disable Xdebug on a machine", RunE: func(cmd *cobra.Command, args []string) error { - name := config.GetString("name", flagMachineName) + machine := flagMachineName + php := config.GetString("php", flagPhpVersion) if err := validate.PHPVersion(php); err != nil { return err } - disableXdebugAction, err := nitro.DisableXdebug(name, php) + var actions []nitro.Action + disableXdebugAction, err := nitro.DisableXdebug(machine, php) + if err != nil { + return err + } + actions = append(actions, *disableXdebugAction) + + restartPhpFpmAction, err := nitro.RestartPhpFpm(machine, php) if err != nil { return err } + actions = append(actions, *restartPhpFpmAction) - return nitro.Run(nitro.NewMultipassRunner("multipass"), []nitro.Action{*disableXdebugAction}) + return nitro.Run(nitro.NewMultipassRunner("multipass"), actions) }, } diff --git a/internal/cmd/xdebug_on.go b/internal/cmd/xdebug_on.go index a435d544..e4d82ff7 100644 --- a/internal/cmd/xdebug_on.go +++ b/internal/cmd/xdebug_on.go @@ -13,13 +13,14 @@ var xdebugOnCommand = &cobra.Command{ Aliases: []string{"xon"}, Short: "Enable Xdebug on a machine", RunE: func(cmd *cobra.Command, args []string) error { - name := config.GetString("name", flagMachineName) + machine := flagMachineName + php := config.GetString("php", flagPhpVersion) if err := validate.PHPVersion(php); err != nil { return err } - enableXdebugAction, err := nitro.EnableXdebug(name, php) + enableXdebugAction, err := nitro.EnableXdebug(machine, php) if err != nil { return err } diff --git a/internal/hack/apply_diff.go b/internal/diff/diff.go similarity index 87% rename from internal/hack/apply_diff.go rename to internal/diff/diff.go index c6540671..0d7dd99c 100644 --- a/internal/hack/apply_diff.go +++ b/internal/diff/diff.go @@ -1,14 +1,14 @@ -package hack +package diff import ( "github.com/craftcms/nitro/config" "github.com/craftcms/nitro/internal/nitro" ) -// MountDiffActions takes a machine name and currently attached mounts and mounts from +// MountActions takes a machine name and currently attached mounts and mounts from // the config file. It will then compare the two and determine if we need to add or // remove mounts from the machine and return the appropriate actions. -func MountDiffActions(name string, attached, file []config.Mount) ([]nitro.Action, error) { +func MountActions(name string, attached, file []config.Mount) ([]nitro.Action, error) { var actions []nitro.Action // if there are more attached than file, we need to run the remove commands diff --git a/internal/hack/apply_diff_test.go b/internal/diff/diff_test.go similarity index 88% rename from internal/hack/apply_diff_test.go rename to internal/diff/diff_test.go index 3eec465f..2091ae41 100644 --- a/internal/hack/apply_diff_test.go +++ b/internal/diff/diff_test.go @@ -1,4 +1,4 @@ -package hack +package diff import ( "reflect" @@ -83,13 +83,13 @@ func TestMountDiffActions(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got, err := MountDiffActions(tt.args.name, tt.args.attached, tt.args.file) + got, err := MountActions(tt.args.name, tt.args.attached, tt.args.file) if (err != nil) != tt.wantErr { - t.Errorf("MountDiffActions() error = %v, wantErr %v", err, tt.wantErr) + t.Errorf("MountActions() error = %v, wantErr %v", err, tt.wantErr) return } if !reflect.DeepEqual(got, tt.want) { - t.Errorf("MountDiffActions() got = \n%v, \nwant \n%v", got, tt.want) + t.Errorf("MountActions() got = \n%v, \nwant \n%v", got, tt.want) } }) } diff --git a/internal/hack/testdata/another/example/.gitkeep b/internal/diff/testdata/mounts/attached/.gitkeep similarity index 100% rename from internal/hack/testdata/another/example/.gitkeep rename to internal/diff/testdata/mounts/attached/.gitkeep diff --git a/internal/hack/find_mounts.go b/internal/find/find.go similarity index 51% rename from internal/hack/find_mounts.go rename to internal/find/find.go index d935995a..105ddef9 100644 --- a/internal/hack/find_mounts.go +++ b/internal/find/find.go @@ -1,18 +1,20 @@ -package hack +package find import ( "bytes" "encoding/csv" + "fmt" + "os/exec" "strings" "github.com/craftcms/nitro/config" ) -// FindMounts will take a name of a machine and the output of an exec.Command as a slice of bytes +// Mounts will take a name of a machine and the output of an exec.Command as a slice of bytes // and return a slice of config mounts that has a source and destination or an error. This is // used to match if the machine has any mounts. The args passed to multipass are expected to // return a csv format (e.g. "multipass info machinename --format=csv"). -func FindMounts(name string, b []byte) ([]config.Mount, error) { +func Mounts(name string, b []byte) ([]config.Mount, error) { var mounts []config.Mount records, err := csv.NewReader(bytes.NewReader(b)).ReadAll() @@ -39,3 +41,27 @@ func FindMounts(name string, b []byte) ([]config.Mount, error) { return mounts, nil } + +func ContainersToCreate(machine string, cfg config.Config) ([]config.Database, error) { + path, err := exec.LookPath("multipass") + if err != nil { + return nil, err + } + + var dbs []config.Database + for _, db := range cfg.Databases { + container := fmt.Sprintf("%s_%s_%s", db.Engine, db.Version, db.Port) + + c := exec.Command(path, []string{"exec", machine, "--", "sudo", "bash", "/opt/nitro/scripts/docker-container-exists.sh", container}...) + output, err := c.Output() + if err != nil { + return nil, err + } + + if !strings.Contains(string(output), "exists") { + dbs = append(dbs, db) + } + } + + return dbs, nil +} diff --git a/internal/helpers/file.go b/internal/helpers/file.go index f180ab19..c2394e0b 100644 --- a/internal/helpers/file.go +++ b/internal/helpers/file.go @@ -7,7 +7,7 @@ import ( func CreateFileIfNotExist(filename string) error { // does it exist - if fileExists(filename) { + if FileExists(filename) { return errors.New("file already exists") } @@ -17,10 +17,10 @@ func CreateFileIfNotExist(filename string) error { return err } -func fileExists(filename string) bool { +func FileExists(filename string) bool { info, err := os.Stat(filename) if os.IsNotExist(err) { return false } return !info.IsDir() -} \ No newline at end of file +} diff --git a/internal/hack/testdata/mounts/attached/.gitkeep b/internal/helpers/testdata/normalize-path/somefile.txt similarity index 100% rename from internal/hack/testdata/mounts/attached/.gitkeep rename to internal/helpers/testdata/normalize-path/somefile.txt diff --git a/internal/helpers/webroot.go b/internal/helpers/webroot.go deleted file mode 100644 index a89d7926..00000000 --- a/internal/helpers/webroot.go +++ /dev/null @@ -1,37 +0,0 @@ -package helpers - -import ( - "errors" - "os" - "path/filepath" -) - -func FindWebRoot(path string) (string, error) { - var webroot string - if err := filepath.Walk(path, func(p string, info os.FileInfo, err error) error { - if !info.IsDir() { - return nil - } - - switch info.Name() { - case "web": - webroot = info.Name() - case "public": - webroot = info.Name() - case "public_html": - webroot = info.Name() - case "www": - webroot = info.Name() - } - - return nil - }); err != nil { - return "", err - } - - if webroot == "" { - return "", errors.New("unable to locate the webroot") - } - - return webroot, nil -} diff --git a/internal/hosts/hosts.go b/internal/hosts/hosts.go new file mode 100644 index 00000000..5a829c10 --- /dev/null +++ b/internal/hosts/hosts.go @@ -0,0 +1,44 @@ +package hosts + +import ( + "fmt" +) + +// AdderSaver is an interface for adding and saving hosts +type AdderSaver interface { + AddHosts(address string, domains []string) + Save() error +} + +type RemoverSaver interface { + RemoveHosts(domains []string) + Save() error +} + +// Add takes an address and a list of hosts, or domains, to +// add to a hosts file. It uses an AdderSaver, which uses +// the functionality of the library used to edit hosts. +func Add(a AdderSaver, address string, domains []string) error { + if domains == nil { + fmt.Println("No sites to add to hosts file, skipping...") + return nil + } + + a.AddHosts(address, domains) + + return a.Save() +} + +// Remove takes a list of domains that should be removed +// from the hosts file. It uses a RemoverSaver, which +// uses the functionality of the lib to edit hosts. +func Remove(r RemoverSaver, domains []string) error { + if domains == nil { + fmt.Println("No sites to remove from hosts file, skipping...") + return nil + } + + r.RemoveHosts(domains) + + return r.Save() +} diff --git a/internal/nitro/destroy.go b/internal/nitro/destroy.go index 0ca6bc90..3900a1a2 100644 --- a/internal/nitro/destroy.go +++ b/internal/nitro/destroy.go @@ -1,18 +1,10 @@ package nitro // Destroy will destroy a machine, with an option to permanently delete it. -func Destroy(name string, force bool) (*Action, error) { - if force { - return &Action{ - Type: "delete", - UseSyscall: false, - Args: []string{"delete", name, "-p"}, - }, nil - } - +func Destroy(name string) (*Action, error) { return &Action{ Type: "delete", UseSyscall: false, - Args: []string{"delete", name}, + Args: []string{"delete", name, "-p"}, }, nil } diff --git a/internal/nitro/destroy_test.go b/internal/nitro/destroy_test.go index 349d628b..21efbb15 100644 --- a/internal/nitro/destroy_test.go +++ b/internal/nitro/destroy_test.go @@ -16,23 +16,10 @@ func TestDestroy(t *testing.T) { want *Action wantErr bool }{ - { - name: "can destroy a machine", - args: args{ - name: "notpermanent", - force: false, - }, - want: &Action{ - Type: "delete", - UseSyscall: false, - Args: []string{"delete", "notpermanent"}, - }, - }, { name: "can destroy a machine permanently", args: args{ name: "ispermanent", - force: true, }, want: &Action{ Type: "delete", @@ -43,7 +30,7 @@ func TestDestroy(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got, err := Destroy(tt.args.name, tt.args.force) + got, err := Destroy(tt.args.name) if (err != nil) != tt.wantErr { t.Errorf("Destroy() error = %v, wantErr %v", err, tt.wantErr) return diff --git a/internal/nitro/docker.go b/internal/nitro/docker.go index bd321765..9ed4db51 100644 --- a/internal/nitro/docker.go +++ b/internal/nitro/docker.go @@ -22,11 +22,11 @@ func CreateDatabaseContainer(name, engine, version, port string) (*Action, error case "postgres": containerPort = "5432" containerPath = "/var/lib/postgresql/data" - containerEnvVars = []string{"-e", "POSTGRES_PASSWORD=nitro", "-e", "POSTGRES_USER=nitro", "-e", "POSTGRES_DB=nitro"} + containerEnvVars = []string{"-e", "POSTGRES_PASSWORD=nitro", "-e", "POSTGRES_USER=nitro"} default: containerPort = "3306" containerPath = "/var/lib/mysql" - containerEnvVars = []string{"-e", "MYSQL_ROOT_PASSWORD=nitro", "-e", "MYSQL_DATABASE=nitro", "-e", "MYSQL_USER=nitro", "-e", "MYSQL_PASSWORD=nitro"} + containerEnvVars = []string{"-e", "MYSQL_ROOT_PASSWORD=nitro", "-e", "MYSQL_USER=nitro", "-e", "MYSQL_PASSWORD=nitro"} } // create the volumeMount path using the engine, version, and port diff --git a/internal/nitro/docker_test.go b/internal/nitro/docker_test.go index cac034b4..614e62fc 100644 --- a/internal/nitro/docker_test.go +++ b/internal/nitro/docker_test.go @@ -29,7 +29,7 @@ func TestCreateDatabaseContainer(t *testing.T) { want: &Action{ Type: "exec", UseSyscall: false, - Args: []string{"exec", "machinename", "--", "docker", "run", "-v", "mysql_5.7_3306:/var/lib/mysql", "--name", "mysql_5.7_3306", "-d", "--restart=always", "-p", "3306:3306", "-e", "MYSQL_ROOT_PASSWORD=nitro", "-e", "MYSQL_DATABASE=nitro", "-e", "MYSQL_USER=nitro", "-e", "MYSQL_PASSWORD=nitro", "mysql:5.7"}, + Args: []string{"exec", "machinename", "--", "docker", "run", "-v", "mysql_5.7_3306:/var/lib/mysql", "--name", "mysql_5.7_3306", "-d", "--restart=always", "-p", "3306:3306", "-e", "MYSQL_ROOT_PASSWORD=nitro", "-e", "MYSQL_USER=nitro", "-e", "MYSQL_PASSWORD=nitro", "mysql:5.7"}, }, wantErr: false, }, @@ -44,7 +44,7 @@ func TestCreateDatabaseContainer(t *testing.T) { want: &Action{ Type: "exec", UseSyscall: false, - Args: []string{"exec", "postgresmachine", "--", "docker", "run", "-v", "postgres_11.7_5432:/var/lib/postgresql/data", "--name", "postgres_11.7_5432", "-d", "--restart=always", "-p", "5432:5432", "-e", "POSTGRES_PASSWORD=nitro", "-e", "POSTGRES_USER=nitro", "-e", "POSTGRES_DB=nitro", "postgres:11.7"}, + Args: []string{"exec", "postgresmachine", "--", "docker", "run", "-v", "postgres_11.7_5432:/var/lib/postgresql/data", "--name", "postgres_11.7_5432", "-d", "--restart=always", "-p", "5432:5432", "-e", "POSTGRES_PASSWORD=nitro", "-e", "POSTGRES_USER=nitro", "postgres:11.7"}, }, wantErr: false, }, diff --git a/internal/nitro/redis.go b/internal/nitro/redis.go index e7fc0d26..6e7fc859 100644 --- a/internal/nitro/redis.go +++ b/internal/nitro/redis.go @@ -9,7 +9,7 @@ func Redis(name string) (*Action, error) { return &Action{ Type: "exec", - UseSyscall: false, + UseSyscall: true, Args: []string{"exec", name, "--", "redis-cli"}, }, nil } diff --git a/internal/nitro/redis_test.go b/internal/nitro/redis_test.go index a1b1e143..b43f808c 100644 --- a/internal/nitro/redis_test.go +++ b/internal/nitro/redis_test.go @@ -20,7 +20,7 @@ func TestRedis(t *testing.T) { args: args{name: "somename"}, want: &Action{ Type: "exec", - UseSyscall: false, + UseSyscall: true, Args: []string{"exec", "somename", "--", "redis-cli"}, }, wantErr: false, diff --git a/internal/nitro/run.go b/internal/nitro/run.go index 65dd6253..012ffbd5 100644 --- a/internal/nitro/run.go +++ b/internal/nitro/run.go @@ -18,4 +18,4 @@ func Run(r ShellRunner, actions []Action) error { } return nil -} +} \ No newline at end of file diff --git a/internal/nitro/versions.go b/internal/nitro/versions.go index 3f631096..7ce70076 100644 --- a/internal/nitro/versions.go +++ b/internal/nitro/versions.go @@ -4,7 +4,7 @@ var ( PHPVersions = []string{"7.4", "7.3", "7.2", "7.1", "7.0"} DBEngines = []string{"mysql", "postgres"} DBVersions = map[string][]string{ - "mysql": {"8.0", "5.8", "5.7", "5.6", "5"}, + "mysql": {"8.0", "5.7", "5.6", "5"}, "postgres": {"12", "12.2", "11.7", "11", "10.12", "10", "9.6", "9.6", "9"}, } ) diff --git a/internal/normalize/normalize.go b/internal/normalize/normalize.go new file mode 100644 index 00000000..7dcc071b --- /dev/null +++ b/internal/normalize/normalize.go @@ -0,0 +1,27 @@ +package normalize + +import ( + "os" + "path/filepath" + "strings" +) + +// Path is responsible for taking a path, relative +// or otherwise, and returning the name of the file, +// the absolute path, and an error if not found. +func Path(path, home string) (string, string, error) { + p := strings.Split(path, string(os.PathSeparator)) + + if strings.Contains(p[0], "~") {p[0] = home + path = strings.Join(p, string(os.PathSeparator)) + } + + absPath, err := filepath.Abs(path) + if err != nil { + return "", "", err + } + + filename := p[len(p)-1] + + return filename, absPath, nil +} diff --git a/internal/normalize/normalize_test.go b/internal/normalize/normalize_test.go new file mode 100644 index 00000000..22b09634 --- /dev/null +++ b/internal/normalize/normalize_test.go @@ -0,0 +1,61 @@ +package normalize + +import ( + "os" + "strings" + "testing" + + "github.com/mitchellh/go-homedir" +) + +func TestNormalizePath(t *testing.T) { + home, err := homedir.Dir() + currentDir, err := os.Getwd() + if err != nil { + t.Fatal(err) + } + projectDir := strings.Replace(currentDir, home, "~", 1) + + type args struct { + path string + home string + } + tests := []struct { + name string + args args + homedir string + wantFilename string + wantFileAbsPath string + wantErr bool + }{ + { + name: "will resolve as a full path", + args: args{path: "testdata/normalize-path/somefile.txt", home: home}, + wantFilename: "somefile.txt", + wantFileAbsPath: currentDir + "/testdata/normalize-path/somefile.txt", + wantErr: false, + }, + { + name: "will resolve ~ as a full path", + args: args{path: projectDir + "/testdata/normalize-path/somefile.txt", home: home}, + wantFilename: "somefile.txt", + wantFileAbsPath: currentDir + "/testdata/normalize-path/somefile.txt", + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, got1, err := Path(tt.args.path, tt.args.home) + if (err != nil) != tt.wantErr { + t.Errorf("Path() error = %v, wantErr %v", err, tt.wantErr) + return + } + if got != tt.wantFilename { + t.Errorf("Path() got = \n%v, \nwant \n%v", got, tt.wantFilename) + } + if got1 != tt.wantFileAbsPath { + t.Errorf("Path() got1 = \n%v, \nwant \n%v", got1, tt.wantFileAbsPath) + } + }) + } +} diff --git a/internal/prompt/prompt.go b/internal/prompt/prompt.go index 563cf671..95966dfa 100644 --- a/internal/prompt/prompt.go +++ b/internal/prompt/prompt.go @@ -1,6 +1,8 @@ package prompt -import "github.com/manifoldco/promptui" +import ( + "github.com/manifoldco/promptui" +) func Ask(label, def string, validator promptui.ValidateFunc) (string, error) { p := promptui.Prompt{ @@ -17,6 +19,36 @@ func Ask(label, def string, validator promptui.ValidateFunc) (string, error) { return v, nil } +func AskWithDefault(label, def string, validator promptui.ValidateFunc) (string, error) { + p := promptui.Prompt{ + Label: label + " [" + def + "]", + Validate: validator, + } + + v, err := p.Run() + if err != nil { + return "", err + } + + switch v { + case "": + v = def + } + + return v, nil +} + +func SelectWithDefault(label, def string, options []string) (int, string) { + p := promptui.Select{ + Label: label + " [" + def + "]", + Items: options, + } + + i, selected, _ := p.Run() + + return i, selected +} + func Select(label string, options []string) (int, string) { p := promptui.Select{ Label: label, diff --git a/internal/sudo/sudo.go b/internal/sudo/sudo.go new file mode 100644 index 00000000..ac3e861f --- /dev/null +++ b/internal/sudo/sudo.go @@ -0,0 +1,22 @@ +package sudo + +import ( + "os" + "os/exec" +) + +func RunCommand(nitro, machine string, commands ...string) error { + // TODO update this for windows support + + b := []string{nitro, "-m", machine} + for _, command := range commands { + b = append(b, command) + } + + c := exec.Command("sudo", b...) + + c.Stdout = os.Stdout + c.Stderr = os.Stderr + + return c.Run() +} diff --git a/internal/hack/version.go b/internal/version/version.go similarity index 93% rename from internal/hack/version.go rename to internal/version/version.go index c3b62b5c..70a19049 100644 --- a/internal/hack/version.go +++ b/internal/version/version.go @@ -1,4 +1,4 @@ -package hack +package version import ( "encoding/json" @@ -81,8 +81,10 @@ type releaseList []struct { Body string `json:"body"` } -// https://api.github.com/repos/master/nitro/releases -func GetLatestVersion(client *http.Client, url string) (string, error) { +// GetLatest takes a http client and URL. It is responsible for +// communicating with the Github API and returning the string +// representation of the latest nitro release or an error. +func GetLatest(client *http.Client, url string) (string, error) { req, err := http.NewRequest("GET", url, nil) if err != nil { return "", err diff --git a/internal/helpers/testdata/good-example/web/.gitkeep b/internal/webroot/testdata/good-example/web/.gitkeep similarity index 100% rename from internal/helpers/testdata/good-example/web/.gitkeep rename to internal/webroot/testdata/good-example/web/.gitkeep diff --git a/internal/helpers/testdata/public-example/public/.gitkeep b/internal/webroot/testdata/public-example/public/.gitkeep similarity index 100% rename from internal/helpers/testdata/public-example/public/.gitkeep rename to internal/webroot/testdata/public-example/public/.gitkeep diff --git a/internal/helpers/testdata/public_html-example/public_html/.gitkeep b/internal/webroot/testdata/public_html-example/public_html/.gitkeep similarity index 100% rename from internal/helpers/testdata/public_html-example/public_html/.gitkeep rename to internal/webroot/testdata/public_html-example/public_html/.gitkeep diff --git a/internal/helpers/testdata/www-example/www/.gitkeep b/internal/webroot/testdata/www-example/www/.gitkeep similarity index 100% rename from internal/helpers/testdata/www-example/www/.gitkeep rename to internal/webroot/testdata/www-example/www/.gitkeep diff --git a/internal/webroot/webroot.go b/internal/webroot/webroot.go new file mode 100644 index 00000000..a5500a71 --- /dev/null +++ b/internal/webroot/webroot.go @@ -0,0 +1,44 @@ +package webroot + +import ( + "errors" + "os" + "path/filepath" +) + +// Find takes a directory and will search for the "webroot" automatically. +// if it cannot find a know webroot, the func will return an error. This is +// used when determining a sites complete path to the webroot for nginx. +func Find(path string) (string, error) { + var w string + if err := filepath.Walk(path, func(p string, info os.FileInfo, err error) error { + if info == nil { + return nil + } + + if !info.IsDir() { + return nil + } + + switch info.Name() { + case "web": + w = info.Name() + case "public": + w = info.Name() + case "public_html": + w = info.Name() + case "www": + w = info.Name() + } + + return nil + }); err != nil { + return "", err + } + + if w == "" { + return "", errors.New("unable to locate the webroot for " + path) + } + + return w, nil +} diff --git a/internal/helpers/webroot_test.go b/internal/webroot/webroot_test.go similarity index 82% rename from internal/helpers/webroot_test.go rename to internal/webroot/webroot_test.go index 88e13d21..1c7658a3 100644 --- a/internal/helpers/webroot_test.go +++ b/internal/webroot/webroot_test.go @@ -1,4 +1,4 @@ -package helpers +package webroot import "testing" @@ -47,13 +47,13 @@ func TestFindWebRoot(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got, err := FindWebRoot(tt.args.path) + got, err := Find(tt.args.path) if (err != nil) != tt.wantErr { - t.Errorf("FindWebRoot() error = %v, wantErr %v", err, tt.wantErr) + t.Errorf("Find() error = %v, wantErr %v", err, tt.wantErr) return } if got != tt.want { - t.Errorf("FindWebRoot() got = %v, want %v", got, tt.want) + t.Errorf("Find() got = %v, want %v", got, tt.want) } }) }