diff --git a/.github/workflows/pygmy.yml b/.github/workflows/pygmy.yml index 71bd7413..dee764bf 100644 --- a/.github/workflows/pygmy.yml +++ b/.github/workflows/pygmy.yml @@ -48,7 +48,7 @@ jobs: # - name: Switch Docker daemon mode to Linux # run: Start-Process $Env:ProgramFiles\Docker\Docker\DockerCli.exe -ArgumentList "-SwitchLinuxEngine" # - name: Build -# run: docker build -t pygmy-go . +# run: docker build -t pygmy. # - name: Run status command # run: builds/${PYGMY_PATH} --config examples/pygmy.basic.yml status; # - name: Run version command @@ -82,29 +82,33 @@ jobs: sudo dpkg -i --ignore-depends=docker-ce lando-stable.deb; - name: Compile - run: go build -o pygmy-go-linux-amd64 + run: go build -o pygmy-linux-amd64 - name: Basic test run: | - ./pygmy-go-linux-amd64 pull; - ./pygmy-go-linux-amd64 pull; - ./pygmy-go-linux-amd64 status; - ./pygmy-go-linux-amd64 --config examples/pygmy.basic.yml up; - ./pygmy-go-linux-amd64 --config examples/pygmy.basic.yml status; - ./pygmy-go-linux-amd64 --config examples/pygmy.basic.yml version; + ./pygmy-linux-amd64 pull; + ./pygmy-linux-amd64 pull; + ./pygmy-linux-amd64 status; + ./pygmy-linux-amd64 --config examples/pygmy.basic.yml up; + ./pygmy-linux-amd64 --config examples/pygmy.basic.yml status; + ./pygmy-linux-amd64 --config examples/pygmy.basic.yml version; - name: Show pygmy image versions run: | docker ps -a --filter "label=pygmy.name" + - name: Export configuration + run: ./pygmy-linux-amd64 export -o ./exported-config.yml + + - name: Show configuration + run: cat ./exported-config.yml + - name: SSH Key test run: | - ./pygmy-go-linux-amd64 addkey /home/runner/.ssh/id_rsa.pub; - ./pygmy-go-linux-amd64 addkey /home/runner/.ssh/id_pwd.pub; - ./pygmy-go-linux-amd64 status; - ./pygmy-go-linux-amd64 status | grep 'id_rsa'; + ./pygmy-linux-amd64 addkey /home/runner/.ssh/id_rsa.pub; + ./pygmy-linux-amd64 status; + ./pygmy-linux-amd64 status | grep 'id_rsa'; docker run --rm -i --volumes-from amazeeio-ssh-agent uselagoon/php-7.4-cli /usr/bin/ssh-add -l | grep 'id_rsa'; -# ./pygmy-go-linux-amd64 status | grep 'id_pwd'; - name: Resolv file test run: | @@ -151,7 +155,7 @@ jobs: sleep 5; curl --HEAD http://drupal9-example-advanced.docker.amazee.io; curl --HEAD http://drupal9-example-advanced.docker.amazee.io | grep "X-LAGOON"; - ../../pygmy-go-linux-amd64 --config ../../examples/pygmy.basic.yml status | grep '\- http://drupal9-example-advanced.docker.amazee.io'; + ../../pygmy-linux-amd64 --config ../../examples/pygmy.basic.yml status | grep '\- http://drupal9-example-advanced.docker.amazee.io'; docker-compose -p drupal9-advanced down; docker-compose -p drupal9-advanced rm; cd ../../; @@ -164,7 +168,7 @@ jobs: sleep 5; curl --HEAD http://drupal9-base.docker.amazee.io; curl --HEAD http://drupal9-base.docker.amazee.io | grep "X-LAGOON"; - ../../pygmy-go-linux-amd64 --config ../../examples/pygmy.basic.yml status | grep '\- http://drupal9-base.docker.amazee.io'; + ../../pygmy-linux-amd64 --config ../../examples/pygmy.basic.yml status | grep '\- http://drupal9-base.docker.amazee.io'; docker-compose -p drupal-base down; docker-compose -p drupal-base rm; cd ../../; @@ -177,7 +181,7 @@ jobs: sleep 5; curl --HEAD http://drupal9-postgres.docker.amazee.io; curl --HEAD http://drupal9-postgres.docker.amazee.io | grep "X-LAGOON"; - ../../pygmy-go-linux-amd64 --config ../../examples/pygmy.basic.yml status | grep '\- http://drupal9-postgres.docker.amazee.io'; + ../../pygmy-linux-amd64 --config ../../examples/pygmy.basic.yml status | grep '\- http://drupal9-postgres.docker.amazee.io'; docker-compose -p drupal-postgres down; docker-compose -p drupal-postgres rm; cd ../../; @@ -189,7 +193,7 @@ jobs: docker-compose -p node up -d; curl --HEAD http://node.docker.amazee.io; curl --HEAD http://node.docker.amazee.io | grep "X-LAGOON"; - ../../pygmy-go-linux-amd64 --config ../../examples/pygmy.basic.yml status | grep '\- http://node.docker.amazee.io'; + ../../pygmy-linux-amd64 --config ../../examples/pygmy.basic.yml status | grep '\- http://node.docker.amazee.io'; docker-compose -p node down; docker-compose -p node rm; cd ../../; @@ -227,7 +231,7 @@ jobs: sleep 5; curl --HEAD http://wordpress-example-simple.docker.amazee.io; curl --HEAD http://wordpress-example-simple.docker.amazee.io | grep "X-LAGOON"; - ../../pygmy-go-linux-amd64 --config ../../examples/pygmy.basic.yml status | grep '\- http://wordpress-example-simple.docker.amazee.io'; + ../../pygmy-linux-amd64 --config ../../examples/pygmy.basic.yml status | grep '\- http://wordpress-example-simple.docker.amazee.io'; docker-compose -p wordpress-simple down; docker-compose -p wordpress-simple rm; cd ../../; @@ -244,17 +248,17 @@ jobs: - name: Test the down command run: | - ./pygmy-go-linux-amd64 --config examples/pygmy.basic.yml down | grep 'Successfully stopped amazeeio'; - ./pygmy-go-linux-amd64 --config examples/pygmy.basic.yml status | grep '\[ \] amazeeio-' | grep 'is not running'; - ./pygmy-go-linux-amd64 --config examples/pygmy.basic.yml status | grep 'Running as container amazeeio-' && false || true; - ./pygmy-go-linux-amd64 --config examples/pygmy.basic.yml up; - ./pygmy-go-linux-amd64 --config examples/pygmy.basic.yml status | grep 'Running as container amazeeio-' && true || false; + ./pygmy-linux-amd64 --config examples/pygmy.basic.yml down | grep 'Successfully stopped amazeeio'; + ./pygmy-linux-amd64 --config examples/pygmy.basic.yml status | grep '\[ \] amazeeio-' | grep 'is not running'; + ./pygmy-linux-amd64 --config examples/pygmy.basic.yml status | grep 'Running as container amazeeio-' && false || true; + ./pygmy-linux-amd64 --config examples/pygmy.basic.yml up; + ./pygmy-linux-amd64 --config examples/pygmy.basic.yml status | grep 'Running as container amazeeio-' && true || false; - name: Cowsay test - run: ./pygmy-go-linux-amd64 --config examples/pygmy.basic.yml up | grep 'holy ship' || true; + run: ./pygmy-linux-amd64 --config examples/pygmy.basic.yml up | grep 'holy ship' || true; - name: Cleanup pygmy - run: ./pygmy-go-linux-amd64 clean; + run: ./pygmy-linux-amd64 clean; - name: Cleanup after tests. run: | diff --git a/.goreleaser.yml b/.goreleaser.yml index a94eb8f3..e95ce016 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -3,16 +3,16 @@ env: - GOPROXY=https://gocenter.io archives: - - id: pygmy-go + - id: pygmy name_template: "{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}" builds: - - pygmy-go - - id: pygmy-go-static + - pygmy + - id: pygmy-static name_template: "{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}_static" builds: - - pygmy-go-static + - pygmy-static builds: - - id: pygmy-go + - id: pygmy env: - CGO_ENABLED=0 goos: @@ -24,7 +24,7 @@ builds: - amd64 - arm - arm64 - - id: pygmy-go-static + - id: pygmy-static env: - CGO_ENABLED=0 flags: @@ -41,7 +41,7 @@ builds: brews: - ids: - - pygmy-go + - pygmy tap: owner: pygmystack name: homebrew-pygmy @@ -49,5 +49,5 @@ brews: homepage: "https://github.com/pygmystack/pygmy" description: "amazee.io's local development helper tool" skip_upload: false - test: system "#{bin}/pygmy-go version" - install: bin.install "pygmy-go" + test: system "#{bin}/pygmy version" + install: bin.install "pygmy" diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 519bb25b..00000000 --- a/.travis.yml +++ /dev/null @@ -1,29 +0,0 @@ ---- -language: go -go: - - "1.14.2" - -matrix: - include: - - - name: Windows build - os: windows - services: docker - before_install: - - go mod download - - GO111MODULE=on go vet $(go list ./...); - - script: - - export PYGMY_PATH=pygmy-go.exe; - - - go mod vendor - - rm -f go.mod - - rm -f go.sum - - go build -o pygmy-go.exe . - - cp pygmy-go.exe builds/pygmy-go.exe - - - builds/${PYGMY_PATH} --config examples/pygmy.basic.yml status; - - builds/${PYGMY_PATH} --config examples/pygmy.basic.yml version; - -notifications: - slack: fubarhouse:upHoIzmKb4ikkBOt2cOwgKXY \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 014b98aa..193d7d9b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -8,28 +8,28 @@ COPY service/ /go/src/github.com/pygmystack/pygmy/service/ WORKDIR /go/src/github.com/pygmystack/pygmy/ RUN GO111MODULE=on go mod verify -RUN GO111MODULE=on GOOS=linux GOARCH=386 go build -o pygmy-go-linux-386 . -RUN GO111MODULE=on GOOS=linux GOARCH=386 CGO_ENABLED=0 go build -a -ldflags '-extldflags "-static"' -o pygmy-go-linux-386-static . -RUN GO111MODULE=on GOOS=linux GOARCH=arm go build -o pygmy-go-linux-arm . -RUN GO111MODULE=on GOOS=linux GOARCH=arm CGO_ENABLED=0 go build -a -ldflags '-extldflags "-static"' -o pygmy-go-linux-arm-static . -RUN GO111MODULE=on GOOS=linux GOARCH=arm64 go build -o pygmy-go-linux-arm64 . -RUN GO111MODULE=on GOOS=linux GOARCH=arm64 CGO_ENABLED=0 go build -a -ldflags '-extldflags "-static"' -o pygmy-go-linux-arm64-static . -RUN GO111MODULE=on GOOS=linux GOARCH=amd64 go build -o pygmy-go-linux-amd64 . -RUN GO111MODULE=on GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -a -ldflags '-extldflags "-static"' -o pygmy-go-linux-amd64-static . -RUN GO111MODULE=on GOOS=darwin GOARCH=amd64 go build -o pygmy-go-darwin-amd64 . -RUN GO111MODULE=on GOOS=darwin GOARCH=arm64 go build -o pygmy-go-darwin-arm64 . -RUN GO111MODULE=on GOOS=windows GOARCH=amd64 go build -o pygmy-go.exe . +RUN GO111MODULE=on GOOS=linux GOARCH=386 go build -o pygmy-linux-386 . +RUN GO111MODULE=on GOOS=linux GOARCH=386 CGO_ENABLED=0 go build -a -ldflags '-extldflags "-static"' -o pygmy-linux-386-static . +RUN GO111MODULE=on GOOS=linux GOARCH=arm go build -o pygmy-linux-arm . +RUN GO111MODULE=on GOOS=linux GOARCH=arm CGO_ENABLED=0 go build -a -ldflags '-extldflags "-static"' -o pygmy-linux-arm-static . +RUN GO111MODULE=on GOOS=linux GOARCH=arm64 go build -o pygmy-linux-arm64 . +RUN GO111MODULE=on GOOS=linux GOARCH=arm64 CGO_ENABLED=0 go build -a -ldflags '-extldflags "-static"' -o pygmy-linux-arm64-static . +RUN GO111MODULE=on GOOS=linux GOARCH=amd64 go build -o pygmy-linux-amd64 . +RUN GO111MODULE=on GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -a -ldflags '-extldflags "-static"' -o pygmy-linux-amd64-static . +RUN GO111MODULE=on GOOS=darwin GOARCH=amd64 go build -o pygmy-darwin-amd64 . +RUN GO111MODULE=on GOOS=darwin GOARCH=arm64 go build -o pygmy-darwin-arm64 . +RUN GO111MODULE=on GOOS=windows GOARCH=amd64 go build -o pygmy.exe . FROM alpine WORKDIR /app -COPY --from=builder /go/src/github.com/pygmystack/pygmy/pygmy-go-linux-386 . -COPY --from=builder /go/src/github.com/pygmystack/pygmy/pygmy-go-linux-386-static . -COPY --from=builder /go/src/github.com/pygmystack/pygmy/pygmy-go-linux-arm . -COPY --from=builder /go/src/github.com/pygmystack/pygmy/pygmy-go-linux-arm-static . -COPY --from=builder /go/src/github.com/pygmystack/pygmy/pygmy-go-linux-arm64 . -COPY --from=builder /go/src/github.com/pygmystack/pygmy/pygmy-go-linux-arm64-static . -COPY --from=builder /go/src/github.com/pygmystack/pygmy/pygmy-go-linux-amd64 . -COPY --from=builder /go/src/github.com/pygmystack/pygmy/pygmy-go-linux-amd64-static . -COPY --from=builder /go/src/github.com/pygmystack/pygmy/pygmy-go-darwin-amd64 . -COPY --from=builder /go/src/github.com/pygmystack/pygmy/pygmy-go-darwin-arm64 . -COPY --from=builder /go/src/github.com/pygmystack/pygmy/pygmy-go.exe . +COPY --from=builder /go/src/github.com/pygmystack/pygmy/pygmy-linux-386 . +COPY --from=builder /go/src/github.com/pygmystack/pygmy/pygmy-linux-386-static . +COPY --from=builder /go/src/github.com/pygmystack/pygmy/pygmy-linux-arm . +COPY --from=builder /go/src/github.com/pygmystack/pygmy/pygmy-linux-arm-static . +COPY --from=builder /go/src/github.com/pygmystack/pygmy/pygmy-linux-arm64 . +COPY --from=builder /go/src/github.com/pygmystack/pygmy/pygmy-linux-arm64-static . +COPY --from=builder /go/src/github.com/pygmystack/pygmy/pygmy-linux-amd64 . +COPY --from=builder /go/src/github.com/pygmystack/pygmy/pygmy-linux-amd64-static . +COPY --from=builder /go/src/github.com/pygmystack/pygmy/pygmy-darwin-amd64 . +COPY --from=builder /go/src/github.com/pygmystack/pygmy/pygmy-darwin-arm64 . +COPY --from=builder /go/src/github.com/pygmystack/pygmy/pygmy.exe . diff --git a/Makefile b/Makefile index 27b3fa0e..26fe31c2 100644 --- a/Makefile +++ b/Makefile @@ -3,16 +3,16 @@ SHELL := /bin/bash DIR := ${CURDIR} build: - docker build -t pygmy-go . + docker build -t pygmy . @echo "Removing binaries from previous build" - docker run --rm -v $(DIR):/data pygmy-go sh -c 'rm -f /data/builds/pygmy-go*' + docker run --rm -v $(DIR):/data pygmy sh -c 'rm -f /data/builds/pygmy*' @echo "Done" @echo "Copying binaries to build directory" - docker run --rm -v $(DIR):/data pygmy-go sh -c 'cp pygmy-g* /data/builds/.' + docker run --rm -v $(DIR):/data pygmy sh -c 'cp pygmy* /data/builds/.' @echo "Done" - @echo "Enjoy using pygmy-go binaries in the $(DIR)/build directory." + @echo "Enjoy using pygmy binaries in the $(DIR)/build directory." clean: - docker image rm -f pygmy-go + docker image rm -f pygmy docker image prune -f --filter="label=stage=builder" diff --git a/README.md b/README.md index a889cb6d..a89617cc 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ past on Windows. ## Is Pygmy running? -These instructions will currently install the new version as `pygmy-go` so that the +These instructions will currently install the new version as `pygmy` so that the old version is still available if you have installed it. With no Pygmy running, you should get "connection refused" when attempting to connect to the local amazee network. @@ -43,13 +43,13 @@ configuration to switch out the `haproxy` image for a compatible one if you'd li **Works for**: Linux, MacOS & Windows ```shell -git clone https://github.com/pygmystack/pygmy.git && cd pygmy-go; +git clone https://github.com/pygmystack/pygmy.git && cd pygmy; make build; -cp ./builds/pygmy-go-darwin /usr/local/bin/pygmy-go; -chmod +x /usr/local/bin/pygmy-go; +cp ./builds/pygmy-darwin /usr/local/bin/pygmy; +chmod +x /usr/local/bin/pygmy; ``` -Pygmy is now an executable as `pygmy-go`, while any existing Pygmy is still executable +Pygmy is now an executable as `pygmy`, while any existing Pygmy is still executable as `pygmy`. Now start Pygmy and use the new `status` command. ### Using Homebrew @@ -65,16 +65,19 @@ brew install pygmy; **Works for**: [Arch-based Linux Distributions](https://wiki.archlinux.org/title/Arch-based_distributions) (Manjaro, Elementary, ArcoLinux etc) -[pygmy-go](https://aur.archlinux.org/packages/pygmy-go/) and [pygmy-go-git](https://aur.archlinux.org/packages/pygmy-go-git/) -are available via the Arch User Repository for Arch-based Linux distributions on the community stream. Unfortunately, -Pygmy is not yet available via other distribution methods, so it is otherwise recommended to use homebrew or compile -from source. +[pygmy](https://aur.archlinux.org/packages/pygmy/), [pygmy-bin](https://aur.archlinux.org/packages/pygmy-bin/) and +[pygmy-git](https://aur.archlinux.org/packages/pygmy-git/) are available via the Arch User Repository for Arch-based +Linux distributions on the community stream. Unfortunately, Pygmy is not yet available via other distribution methods, +so it is otherwise recommended to use homebrew to install it, download a pre-compiled binary from the releases page, or +to compile from source. ```shell -# Install the latest release: -yay -S pygmy-go; +# Freshly compile the latest release: +yay -S pygmy; +# Download the latest release precompiled: +yay -S pygmy-bin; # Download and compile the latest HEAD from GitHub on the main branch: -yay -S pygmy-go-git; +yay -S pygmy-git; ``` ## Usage @@ -121,7 +124,7 @@ It will use `dind` and your local daemon to walk through several tests which sho 1. First clone the project: ``` - git clone https://github.com/pygmystack/pygmy.git pygmy-go && cd pygmy-go + git clone https://github.com/pygmystack/pygmy.git pygmy && cd pygmy ``` 2. Perform any updates as required. 3. Clean the environment. diff --git a/cmd/addkey.go b/cmd/addkey.go index 61adb3c2..a8fbfc08 100644 --- a/cmd/addkey.go +++ b/cmd/addkey.go @@ -22,8 +22,9 @@ package cmd import ( "fmt" - "os" + . "github.com/logrusorgru/aurora" + "github.com/pygmystack/pygmy/service/color" "github.com/pygmystack/pygmy/service/interface/docker" "github.com/pygmystack/pygmy/service/library" "github.com/spf13/cobra" @@ -38,14 +39,16 @@ var addkeyCmd = &cobra.Command{ Run: func(cmd *cobra.Command, args []string) { Key, _ := cmd.Flags().GetString("key") - Keys := []string{} + Passphrase, _ := cmd.Flags().GetString("passphrase") + var Keys []library.Key if Key != "" { - Keys = append(Keys, Key) - } else { - if _, err := os.Stat(os.Args[len(os.Args)-1]); err == os.ErrExist { - Keys = append(c.Keys, os.Args[len(os.Args)-1]) + thisKey := library.Key{ + Path: Key, + Passphrase: Passphrase, } + Keys = append(Keys, thisKey) + } else { if len(Keys) == 0 { library.Setup(&c) Keys = c.Keys @@ -53,8 +56,8 @@ var addkeyCmd = &cobra.Command{ } for _, k := range Keys { - if e := library.SshKeyAdd(c, k); e != nil { - fmt.Println(e) + if e := library.SshKeyAdd(c, k.Path, k.Passphrase); e != nil { + color.Print(Red(fmt.Sprintf("%v\n", e))) } } @@ -74,6 +77,12 @@ var addkeyCmd = &cobra.Command{ func init() { rootCmd.AddCommand(addkeyCmd) - addkeyCmd.Flags().StringP("key", "", "", "Path of SSH key to add") + addkeyCmd.Flags().StringP("key", "k", "", "Path of SSH key to add") + addkeyCmd.Flags().StringP("passphrase", "p", "", "Passphrase of the SSH key to add") + + err := addkeyCmd.Flags().MarkHidden("passphrase") + if err != nil { + fmt.Println(err) + } } diff --git a/cmd/completion.go b/cmd/completion.go index 5538f672..fb0d52f8 100644 --- a/cmd/completion.go +++ b/cmd/completion.go @@ -34,13 +34,13 @@ var completionCmd = &cobra.Command{ Bash: - $ source <(pygmy-go completion bash) + $ source <(pygmy completion bash) # To load completions for each session, execute once: # Linux: - $ pygmy-go completion bash > /etc/bash_completion.d/pygmy-go + $ pygmy completion bash > /etc/bash_completion.d/pygmy # macOS: - $ pygmy-go completion bash > /usr/local/etc/bash_completion.d/pygmy-go + $ pygmy completion bash > /usr/local/etc/bash_completion.d/pygmy Zsh: @@ -50,23 +50,23 @@ Zsh: $ echo "autoload -U compinit; compinit" >> ~/.zshrc # To load completions for each session, execute once: - $ pygmy-go completion zsh > "${fpath[1]}/pygmy-go" + $ pygmy completion zsh > "${fpath[1]}/pygmy" # You will need to start a new shell for this setup to take effect. fish: - $ pygmy-go completion fish | source + $ pygmy completion fish | source # To load completions for each session, execute once: - $ pygmy-go completion fish > ~/.config/fish/completions/pygmy-go.fish + $ pygmy completion fish > ~/.config/fish/completions/pygmy.fish PowerShell: - PS> pygmy-go completion powershell | Out-String | Invoke-Expression + PS> pygmy completion powershell | Out-String | Invoke-Expression # To load completions for every new session, run: - PS> pygmy-go completion powershell > pygmy-go.ps1 + PS> pygmy completion powershell > pygmy.ps1 # and source this file from your PowerShell profile. `, DisableFlagsInUseLine: true, diff --git a/cmd/restart.go b/cmd/restart.go index fca64322..ccd5daba 100644 --- a/cmd/restart.go +++ b/cmd/restart.go @@ -39,20 +39,25 @@ var restartCmd = &cobra.Command{ Run: func(cmd *cobra.Command, args []string) { Key, _ := cmd.Flags().GetString("key") + Passphrase, _ := cmd.Flags().GetString("passphrase") NoKey, _ := cmd.Flags().GetBool("no-addkey") if NoKey { - c.Keys = []string{} + c.Keys = []library.Key{} } else { FoundKey := false for _, v := range c.Keys { - if v == Key { + if v.Path == Key { FoundKey = true } } if !FoundKey { - c.Keys = append(c.Keys, Key) + thisKey := library.Key{ + Path: Key, + Passphrase: Passphrase, + } + c.Keys = append(c.Keys, thisKey) } } @@ -65,10 +70,17 @@ func init() { homedir, _ := homedir.Dir() keypath := fmt.Sprintf("%v%v.ssh%vid_rsa", homedir, string(os.PathSeparator), string(os.PathSeparator)) + var passphrase string rootCmd.AddCommand(restartCmd) restartCmd.Flags().StringP("key", "", keypath, "Path of SSH key to add") + restartCmd.Flags().StringP("passphrase", "", passphrase, "Passphrase of the SSH key to add") restartCmd.Flags().BoolP("no-addkey", "", false, "Skip adding the SSH key") restartCmd.Flags().BoolP("no-resolver", "", false, "Skip adding or removing the Resolver") + err := restartCmd.Flags().MarkHidden("passphrase") + if err != nil { + fmt.Println(err) + } + } diff --git a/cmd/root.go b/cmd/root.go index b16b3958..a91a98bc 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -41,7 +41,7 @@ var ( // rootCmd represents the base command when called without any subcommands var rootCmd = &cobra.Command{ - Use: "pygmy-go", + Use: "pygmy", ValidArgs: validArgs, Short: "Amazeeio's local development tool", Long: `Amazeeio's local development tool, diff --git a/cmd/up.go b/cmd/up.go index 8117d95c..1133b7e3 100644 --- a/cmd/up.go +++ b/cmd/up.go @@ -43,21 +43,26 @@ It includes dnsmasq, haproxy, mailhog, resolv and ssh-agent.`, Run: func(cmd *cobra.Command, args []string) { Key, _ := cmd.Flags().GetString("key") + Passphrase, _ := cmd.Flags().GetString("passphrase") NoKey, _ := cmd.Flags().GetBool("no-addkey") if NoKey { - c.Keys = []string{} + c.Keys = []library.Key{} } else { keyExistsInConfig := false for _, key := range c.Keys { - if key == Key { + if key.Path == Key { keyExistsInConfig = true } } if !keyExistsInConfig { - c.Keys = append(c.Keys, Key) + thisKey := library.Key{ + Path: Key, + Passphrase: Passphrase, + } + c.Keys = append(c.Keys, thisKey) } } @@ -70,10 +75,17 @@ func init() { homedir, _ := homedir.Dir() keypath := fmt.Sprintf("%v%v.ssh%vid_rsa", homedir, string(os.PathSeparator), string(os.PathSeparator)) + var passphrase string rootCmd.AddCommand(upCmd) upCmd.Flags().StringP("key", "", keypath, "Path of SSH key to add") + upCmd.Flags().StringP("passphrase", "", passphrase, "Passphrase of the SSH key to add") upCmd.Flags().BoolP("no-addkey", "", false, "Skip adding the SSH key") upCmd.Flags().BoolP("no-resolver", "", false, "Skip adding or removing the Resolver") + err := upCmd.Flags().MarkHidden("passphrase") + if err != nil { + fmt.Println(err) + } + } diff --git a/docs/installation.md b/docs/installation.md index 1da718fe..3fd58cb4 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -16,8 +16,8 @@ To install it, put the binary into your system's `$PATH` environment variable an The following is an example of how you would do this, note the URL and location may change depending on your needs. ```console -$ wget https://github.com/pygmystack/pygmy/releases/download/v0.1.0/pygmy-go-darwin -$ mv ./pygmy-go-darwin /usr/local/bin/pygmy +$ wget https://github.com/pygmystack/pygmy/releases/download/v0.8.0/pygmy-darwin +$ mv ./pygmy-darwin /usr/local/bin/pygmy $ chmod u+x /usr/local/bin/pygmy ``` diff --git a/go.mod b/go.mod index 6ed5b46d..af93dd7e 100644 --- a/go.mod +++ b/go.mod @@ -17,4 +17,5 @@ require ( github.com/smartystreets/goconvey v1.7.2 github.com/spf13/cobra v1.1.3 github.com/spf13/viper v1.10.1 + golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3 ) diff --git a/go.sum b/go.sum index af64f8eb..bcef61c2 100644 --- a/go.sum +++ b/go.sum @@ -821,6 +821,8 @@ golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3 h1:0es+/5331RGQPcXlMfP+WrnIIS6dNnNRe0WB02W0F4M= +golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -910,6 +912,8 @@ golang.org/x/net v0.0.0-20210410081132-afb366fc7cd1/go.mod h1:9tjilg8BloeKEkVJvy golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d h1:LO7XpTYMwTqxjLcGWPijK3vRXg1aWdlNOVOHRq45d7c= golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 h1:CIJ76btIcR3eFI5EgSo6k1qKw9KJexJuRLI9G7Hp5wE= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= diff --git a/main_test.go b/main_test.go index c13457ee..7d458640 100644 --- a/main_test.go +++ b/main_test.go @@ -15,7 +15,7 @@ import ( const ( dindContainerName = "exampleTestContainer" - binaryReference = "pygmy-go-linux-amd64" + binaryReference = "pygmy-linux-amd64" ) var ( diff --git a/service/library/library.go b/service/library/library.go index b3a3a018..fff5f6ae 100644 --- a/service/library/library.go +++ b/service/library/library.go @@ -16,7 +16,7 @@ import ( // continued abstraction. type Config struct { // Keys are the paths to the Keys which should be added. - Keys []string `yaml:"Keys"` + Keys []Key `yaml:"keys"` // Domain is the default domain suffix to use. Domain string `yaml:"domain"` @@ -39,6 +39,12 @@ type Config struct { Volumes map[string]types.Volume } +// Key is a struct with SSH key details. +type Key struct { + Path string `yaml:"path"` + Passphrase string `yaml:"passphrase"` +} + func mergeService(destination model.Service, src *model.Service) (*model.Service, error) { if err := mergo.Merge(&destination, src, mergo.WithOverride); err != nil { fmt.Println(err) diff --git a/service/library/setup.go b/service/library/setup.go index f26e25d4..7452ff31 100644 --- a/service/library/setup.go +++ b/service/library/setup.go @@ -184,15 +184,40 @@ func Setup(c *Config) { } } - c.SortedServices = make([]string, 0, len(c.Services)) + // Determine the slice of sorted services + c.SortedServices = GetServicesSorted(c) +} + +// GetServicesSorted will return a list of services as plain text. +// due to some weirdness the ssh agent must be the first value. +func GetServicesSorted(c *Config) []string { + + SortedServices := make([]string, 0) + SSHAgentServiceName := "" + + // Do not add ssh-agent in the first run. for key, service := range c.Services { + name, _ := service.GetFieldString("name") + purpose, _ := service.GetFieldString("purpose") weight, _ := service.GetFieldInt("weight") - c.SortedServices = append(c.SortedServices, fmt.Sprintf("%06d|%v", weight, key)) + if purpose == "sshagent" { + SSHAgentServiceName = name + } else { + SortedServices = append(SortedServices, fmt.Sprintf("%06d|%v", weight, key)) + } + } + + // Alphabetical sorting. + sort.Strings(SortedServices) + + // Strip the ordering prefix from the service name + for n, v := range SortedServices { + SortedServices[n] = strings.Split(v, "|")[1] } - sort.Strings(c.SortedServices) - for n, v := range c.SortedServices { - c.SortedServices[n] = strings.Split(v, "|")[1] + if SSHAgentServiceName != "" { + SortedServices = append([]string{SSHAgentServiceName}, SortedServices...) } + return SortedServices } diff --git a/service/library/setup_test.go b/service/library/setup_test.go new file mode 100644 index 00000000..7da39b34 --- /dev/null +++ b/service/library/setup_test.go @@ -0,0 +1,20 @@ +package library_test + +import ( + "testing" + + "github.com/pygmystack/pygmy/service/library" +) + +// Tests that the SSH Agent is first in SortedServices. +func TestSortedServices(t *testing.T) { + c := &library.Config{} + library.Setup(c) + c.SortedServices = library.GetServicesSorted(c) + if c.SortedServices[0] != "amazeeio-ssh-agent" { + t.Fail() + } + for _, v := range c.SortedServices { + t.Log(v) + } +} diff --git a/service/library/sshkeyadd.go b/service/library/sshkeyadd.go index 73293c3a..8363cf5f 100644 --- a/service/library/sshkeyadd.go +++ b/service/library/sshkeyadd.go @@ -6,11 +6,14 @@ import ( "runtime" "strings" + "github.com/logrusorgru/aurora" + + "github.com/pygmystack/pygmy/service/color" "github.com/pygmystack/pygmy/service/ssh/agent" ) // SshKeyAdd will add a given key to the ssh agent. -func SshKeyAdd(c Config, key string) error { +func SshKeyAdd(c Config, key string, passcode string) error { Setup(&c) @@ -26,7 +29,25 @@ func SshKeyAdd(c Config, key string) error { for _, Container := range c.Services { purpose, _ := Container.GetFieldString("purpose") if purpose == "addkeys" { - if !agent.Search(Container, key) { + + // Validate SSH Key before adding. + valid, err := agent.Validate(key, passcode) + if valid { + color.Print(aurora.Green(fmt.Sprintf("Validation success for SSH key %v\n", key))) + + } else { + if err.Error() == "ssh: no key found" { + return fmt.Errorf(fmt.Sprintf("[ ] Validation failure for SSH key %v\n", key)) + } + if err.Error() == "ssh: this private key is passphrase protected" { + return fmt.Errorf(fmt.Sprintf("[ ] Passcode not provided for SSH key %v\n", key)) + } + if err.Error() == "x509: decryption password incorrect" { + return fmt.Errorf(fmt.Sprintf("[ ] Passcode incorrectly provided for SSH key %v\n", key)) + } + } + + if passcode == "" { if runtime.GOOS == "windows" { Container.Config.Cmd = []string{"ssh-add", "/key"} Container.HostConfig.Binds = append(Container.HostConfig.Binds, fmt.Sprintf("%v:/key", key)) @@ -34,26 +55,37 @@ func SshKeyAdd(c Config, key string) error { Container.Config.Cmd = []string{"ssh-add", key} Container.HostConfig.Binds = append(Container.HostConfig.Binds, fmt.Sprintf("%v:%v", key, key)) } - - if e := Container.Create(); e != nil { - return e + } else { + if runtime.GOOS == "windows" { + Container.Config.Cmd = []string{"ssh-add", "/key"} + Container.HostConfig.Binds = append(Container.HostConfig.Binds, fmt.Sprintf("%v:/key", key)) + } else { + Container.Config.Cmd = []string{"ssh-add", key} + Container.HostConfig.Binds = append(Container.HostConfig.Binds, fmt.Sprintf("%v:%v", key, key)) } - if e := Container.Start(); e != nil { - return e + } + + if e := Container.Create(); e != nil { + return e + } + if e := Container.Start(); e != nil { + return e + } + l, _ := Container.DockerLogs() + _ = Container.Remove() + + // We need tighter control on the output of this container... + for _, line := range strings.Split(string(l), "\n") { + if strings.Contains(line, "Identity added:") { + color.Print(aurora.Green(fmt.Sprintf("Successfully added SSH key %v to agent\n", key))) } - l, _ := Container.DockerLogs() - _ = Container.Remove() - - // We need tighter control on the output of this container... - for _, line := range strings.Split(string(l), "\n") { - if strings.Contains(line, "Identity added:") { - fmt.Println(line) - } + if strings.Contains(line, "Enter passphrase for") { + color.Print(aurora.Yellow(fmt.Sprintf("Warning: Passphrase protected SSH keys are not currently supported, the key will not be added.\n"))) } - } } + } return nil } diff --git a/service/library/up.go b/service/library/up.go index ea929fcb..cac05a0f 100644 --- a/service/library/up.go +++ b/service/library/up.go @@ -131,8 +131,8 @@ func Up(c Config) { // Add ssh-keys to the agent if agentPresent { for _, v := range c.Keys { - if e := SshKeyAdd(c, v); e != nil { - fmt.Println(e) + if e := SshKeyAdd(c, v.Path, v.Passphrase); e != nil { + color.Print(Red(fmt.Sprintf("%v\n", e))) } } } diff --git a/service/ssh/agent/ssh_agent.go b/service/ssh/agent/ssh_agent.go index d072d1a3..03227b53 100644 --- a/service/ssh/agent/ssh_agent.go +++ b/service/ssh/agent/ssh_agent.go @@ -1,11 +1,14 @@ package agent import ( + "errors" "fmt" "io/ioutil" "os" "strings" + "golang.org/x/crypto/ssh" + "github.com/docker/docker/api/types/container" "github.com/docker/docker/api/types/network" model "github.com/pygmystack/pygmy/service/interface" @@ -23,7 +26,7 @@ func New() model.Service { "pygmy.network": "amazeeio-network", "pygmy.output": "false", "pygmy.purpose": "sshagent", - "pygmy.weight": "30", + "pygmy.weight": "10", }, }, HostConfig: container.HostConfig{ @@ -52,31 +55,57 @@ func List(service model.Service) ([]byte, error) { return service.DockerLogs() } +// Validate will validate if an SSH key is valid. +func Validate(filePath, passcode string) (bool, error) { + + filePath = strings.TrimRight(filePath, ".pub") + content, err := ioutil.ReadFile(filePath) + if err != nil { + fmt.Println("Err") + } + + if passcode == "" { + _, err = ssh.ParsePrivateKey(content) + if err != nil { + return false, err + } + } else { + _, err = ssh.ParsePrivateKeyWithPassphrase(content, []byte(passcode)) + if err != nil { + return false, err + } + } + + return true, nil +} + // Search will determine if an SSH key has been added to the agent. -func Search(service model.Service, key string) bool { +func Search(service model.Service, key string) (bool, error) { result := false if _, err := os.Stat(key); !os.IsNotExist(err) { stripped := strings.Trim(key, ".pub") data, err := ioutil.ReadFile(stripped + ".pub") if err != nil { - fmt.Println(err) - return false + return false, err } items, _ := List(service) if len(items) == 0 { - return false + return false, nil } for _, item := range strings.Split(string(items), "\n") { if strings.Contains(item, "The agent has no identities") { - return false + return false, errors.New(item) + } + if strings.Contains(item, "Error loading key") { + return false, errors.New(item) } if strings.Contains(item, string(data)) { result = true } } } - return result + return result, nil } diff --git a/service/ssh/agent/ssh_agent_test.go b/service/ssh/agent/ssh_agent_test.go index 5ddc651d..b701fc1d 100644 --- a/service/ssh/agent/ssh_agent_test.go +++ b/service/ssh/agent/ssh_agent_test.go @@ -1,7 +1,6 @@ package agent_test import ( - "fmt" "testing" model "github.com/pygmystack/pygmy/service/interface" @@ -9,21 +8,21 @@ import ( . "github.com/smartystreets/goconvey/convey" ) -func Example() { - agent.New() -} +//func TestExampleList(t *testing.T) { +// m := &model.Service{} +// c, e := agent.List(*m) +// if c != nil && e != nil { +// t.Fail() +// } +//} -func ExampleList() { - _, e := agent.List(model.Service{}) - if e != nil { - fmt.Println(e) +func TestExampleSearch(t *testing.T) { + _, err := agent.Search(model.Service{}, "id_rsa.pub") + if err != nil { + t.Fail() } } -func ExampleSearch() { - agent.Search(model.Service{}, "id_rsa.pub") -} - func Test(t *testing.T) { Convey("SSH Agent: Field equality tests...", t, func() { obj := agent.New() @@ -34,7 +33,7 @@ func Test(t *testing.T) { So(obj.Config.Labels["pygmy.name"], ShouldEqual, "amazeeio-ssh-agent") So(obj.Config.Labels["pygmy.network"], ShouldEqual, "amazeeio-network") So(obj.Config.Labels["pygmy.purpose"], ShouldEqual, "sshagent") - So(obj.Config.Labels["pygmy.weight"], ShouldEqual, "30") + So(obj.Config.Labels["pygmy.weight"], ShouldEqual, "10") So(obj.HostConfig.AutoRemove, ShouldBeFalse) So(obj.HostConfig.IpcMode, ShouldEqual, "private") So(obj.HostConfig.PortBindings, ShouldEqual, nil)