Skip to content

Commit ae99678

Browse files
committed
initial commit
1 parent 24cd8f2 commit ae99678

File tree

17 files changed

+1422
-1
lines changed

17 files changed

+1422
-1
lines changed

.github/workflows/release.yml

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
name: release
2+
on:
3+
push:
4+
tags: [ v*.*.** ] # only a valid semver tag
5+
6+
jobs:
7+
goreleaser:
8+
runs-on: ubuntu-latest
9+
steps:
10+
- name: Checkout
11+
uses: actions/checkout@v2
12+
- name: Unshallow clone
13+
run: git fetch --prune --unshallow
14+
- name: Install Go 1.14
15+
uses: actions/setup-go@v2
16+
with:
17+
go-version: '1.14.x'
18+
- name: Goreleaser publish
19+
uses: goreleaser/goreleaser-action@v1
20+
with:
21+
version: v0.145.0
22+
args: release --rm-dist
23+
env:
24+
env:
25+
GITHUB_TOKEN: ${{ secrets.ACTIONS_TOKEN }}

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,6 @@
1313

1414
# Dependency directories (remove the comment below to include it)
1515
# vendor/
16+
17+
# ide
18+
.idea/

.goreleaser.yml

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
before:
2+
hooks:
3+
- go mod download
4+
builds:
5+
- env:
6+
- CGO_ENABLED=0
7+
- GO111MODULE=on
8+
goos:
9+
- darwin
10+
- windows
11+
- linux
12+
goarch:
13+
- amd64
14+
ldflags: -X github.com/jaxxstorm/ploy/pkg/version.Version={{.Version}}
15+
binary: ploy
16+
main: ./cmd/ploy/main.go
17+
archives:
18+
- name_template: "{{ .Binary }}-{{ .Tag }}-{{ .Os }}-{{ .Arch }}"
19+
format_overrides:
20+
- goos: windows
21+
format: zip
22+
snapshot:
23+
name_template: "{{ .Tag }}-SNAPSHOT"
24+
changelog:
25+
skip: true
26+
release:
27+
prerelease: auto
28+
brews:
29+
-
30+
name: ploy
31+
github:
32+
owner: jaxxstorm
33+
name: homebrew-tap
34+
commit_author:
35+
name: GitHub Actions
36+
37+
folder: Formula
38+
homepage: "https://leebriggs.co.uk"
39+
description: "Quickly install applications to Kubernetes with Pulumi"

README.md

Lines changed: 107 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,107 @@
1-
# ploy
1+
# Ploy - straightforward Kubernetes deployment
2+
3+
Ploy is a proof of concept tool that will deploy a Docker image on your machine locally to a Kubernetes cluster.
4+
5+
It was inspired by [Halloumi](https://github.com/pulumi/halloumi) and its purpose is to show how easy it is to create useful developer tooling with the [Pulumi](https://pulumi.com/) [Automation API](https://pkg.go.dev/github.com/pulumi/pulumi/sdk/v2/go/x/auto)
6+
7+
It provides a Heroku like experience for uses deploying Docker images.
8+
9+
It is currently designed to work exclusively with AWS EKS, but support for other providers could be added.
10+
11+
## Installation
12+
13+
14+
15+
## Usage
16+
17+
Ploy take a Docker context with a `Dockerfile`, builds it locally and pushes it to an [ECR repository](https://docs.aws.amazon.com/AmazonECR/latest/userguide/Repositories.html).
18+
19+
Each ploy application is deployed as a pulumi stack within a project called `ploy` in your configured Pulumi organization (see [configuration](##configuration))
20+
21+
Once the image is pushed, it creates a Kubernetes namespace, Deployment and Service with an external load balancer.
22+
23+
Here's what it looks like:
24+
25+
### Deploy
26+
27+
```bash
28+
ploy up
29+
INFO[0002] Creating application: regularly-viable-stud
30+
INFO[0002] Creating ECR repository
31+
INFO[0002] Creating local docker image
32+
INFO[0002] Creating Kubernetes namespace
33+
INFO[0002] Creating Kubernetes deployment
34+
INFO[0002] Creating Kubernetes service
35+
INFO[0004] Repository created: 616138583583.dkr.ecr.us-west-2.amazonaws.com/regularly-viable-stud-e23717a
36+
INFO[0034] Your service is available at: aedc7304ebcf648baa881cf4069e5aad-354579065.us-west-2.elb.amazonaws.com
37+
```
38+
39+
Ploy deploys your application to the Kubernetes cluster currently configured in your `KUBECONFIG`. It creates the ECR repository using the AWS credentials you're currently using, whether that be an aws profile or aws keys.
40+
41+
_note:_ Your EKS cluster must have access to ECR for the image to be pulled. See [here](https://docs.aws.amazon.com/AmazonECR/latest/userguide/ECR_on_EKS.html) for more details.
42+
43+
You can optionally set an explicit name for your application by passing it as an argument:
44+
45+
```bash
46+
ploy up my-app
47+
```
48+
49+
### Retrieve
50+
51+
You can grab a list of the currently deployed ploy applications using the `get` command:
52+
53+
```bash
54+
ploy get
55+
+--------------------------+--------------------------+----------------------------------------------------------------+-------------------------------------------------------------------------------+
56+
| NAME | LAST UPDATE | DEPLOYMENT INFO | URL |
57+
+--------------------------+--------------------------+----------------------------------------------------------------+-------------------------------------------------------------------------------+
58+
| frequently-better-beagle | 2020-11-03T19:06:36.000Z | https://app.pulumi.com/jaxxstorm/ploy/frequently-better-beagle | http://a9027e3626d3a41a78e600ab832991d3-975080570.us-west-2.elb.amazonaws.com |
59+
| regularly-viable-stud | 2020-11-03T19:41:19.000Z | https://app.pulumi.com/jaxxstorm/ploy/regularly-viable-stud | http://aedc7304ebcf648baa881cf4069e5aad-354579065.us-west-2.elb.amazonaws.com |
60+
+--------------------------+--------------------------+----------------------------------------------------------------+-------------------------------------------------------------------------------+
61+
```
62+
63+
### Destroy
64+
65+
You can tear down your `ploy` application with the `destroy` command:
66+
67+
```bash
68+
ploy destroy
69+
```
70+
71+
## Configuration
72+
73+
Ploy's only required configuration value is your Pulumi org. You can specify it on the command line:
74+
75+
### Organization
76+
77+
```bash
78+
ploy up -o jaxxstorm
79+
```
80+
81+
Or alternatively, set it in your ploy configuration file:
82+
83+
```yaml
84+
cat ~/.ploy/config.yml
85+
org: jaxxstorm
86+
```
87+
88+
### Region
89+
90+
You'll need to set the AWS region you want to use for your ECR repository. You can set it on the command line:
91+
92+
```bash
93+
ploy up -r us-west-2
94+
```
95+
96+
Via configuration:
97+
98+
```yaml
99+
cat ~/.ploy/config.yml
100+
region: us-west-2
101+
```
102+
103+
Or alternatively, it'll read your `AWS_REGION` environment variable:
104+
105+
```
106+
export AWS_REGION=us-west-2
107+
```

cmd/ploy/destroy/cli.go

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
package destroy
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"github.com/manifoldco/promptui"
7+
"github.com/pulumi/pulumi/sdk/v2/go/x/auto"
8+
"github.com/pulumi/pulumi/sdk/v2/go/x/auto/optdestroy"
9+
log "github.com/sirupsen/logrus"
10+
"github.com/spf13/cobra"
11+
"github.com/spf13/viper"
12+
"io/ioutil"
13+
"os"
14+
)
15+
16+
var (
17+
dryrun bool
18+
name string
19+
directory string
20+
verbose bool
21+
)
22+
23+
func Command() *cobra.Command {
24+
command := &cobra.Command{
25+
Use: "destroy",
26+
Short: "Remove your application",
27+
Long: "Remove your application from Kubernetes",
28+
Args: cobra.MinimumNArgs(1),
29+
RunE: func(cmd *cobra.Command, args []string) error {
30+
31+
ctx := context.Background()
32+
org := viper.GetString("org")
33+
region := viper.GetString("region")
34+
name := args[0]
35+
36+
if org == "" {
37+
return fmt.Errorf("must specify pulumi org via flag or config file")
38+
}
39+
40+
label := fmt.Sprintf("This will delete the application %s. Are you sure you wish to continue?", name)
41+
42+
prompt := promptui.Prompt{
43+
Label: label,
44+
IsConfirm: true,
45+
}
46+
47+
result, err := prompt.Run()
48+
49+
if err != nil {
50+
fmt.Printf("User cancelled, not deleting %v\n", err)
51+
os.Exit(0)
52+
}
53+
54+
log.Debug("User confirmed, continuing: %s", result)
55+
log.Infof("Deleting application: %s", name)
56+
57+
// create a stack in our backend
58+
stackName := auto.FullyQualifiedStackName(org, "ploy", name)
59+
// create a stack. We'll set the program shortly
60+
pulumiStack, err := auto.UpsertStackInlineSource(ctx, stackName, "ploy", nil)
61+
if err != nil {
62+
return fmt.Errorf("failed to create or select stack: %v\n", err)
63+
}
64+
65+
// set the AWS region from config
66+
err = pulumiStack.SetConfig(ctx, "aws:region", auto.ConfigValue{Value: region})
67+
if err != nil {
68+
return err
69+
}
70+
71+
// set up workspace and install plugins
72+
workspace := pulumiStack.Workspace()
73+
err = workspace.InstallPlugin(ctx, "aws", "v3.11.0")
74+
if err != nil {
75+
return fmt.Errorf("error installing aws plugin: %v\n", err)
76+
}
77+
err = workspace.InstallPlugin(ctx, "kubernetes", "v2.6.3")
78+
if err != nil {
79+
return fmt.Errorf("error installing kubernetes plugin: %v\n", err)
80+
}
81+
err = workspace.InstallPlugin(ctx, "docker", "v2.4.0")
82+
if err != nil {
83+
return fmt.Errorf("error installing docker plugin: %v\n", err)
84+
}
85+
86+
var streamer optdestroy.Option
87+
if verbose {
88+
streamer = optdestroy.ProgressStreams(os.Stdout)
89+
} else {
90+
streamer = optdestroy.ProgressStreams(ioutil.Discard)
91+
}
92+
_, err = pulumiStack.Destroy(ctx, streamer)
93+
94+
if err != nil {
95+
return fmt.Errorf("error deleting stack resources: %v", err)
96+
}
97+
98+
// destroy the stack so it's no longer listed
99+
// Then we delete the stack from earlier so we don't include it in our list
100+
workspace.RemoveStack(ctx, name)
101+
102+
return nil
103+
104+
},
105+
}
106+
f := command.Flags()
107+
f.BoolVarP(&dryrun, "preview", "p", false, "Preview changes, dry-run mode")
108+
f.BoolVarP(&verbose, "verbose", "v", false, "Show output of Pulumi operations")
109+
f.StringVarP(&directory, "dir", "d", ".", "Path to docker context to use")
110+
111+
viper.BindPFlag("stack", command.Flags().Lookup("stack"))
112+
113+
cobra.MarkFlagRequired(f, "name")
114+
return command
115+
}

cmd/ploy/get/cli.go

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
package get
2+
3+
import (
4+
"context"
5+
"fmt"
6+
n "github.com/jaxxstorm/ploy/pkg/name"
7+
"github.com/olekukonko/tablewriter"
8+
"github.com/pulumi/pulumi/sdk/v2/go/x/auto"
9+
log "github.com/sirupsen/logrus"
10+
"github.com/spf13/cobra"
11+
"github.com/spf13/viper"
12+
"os"
13+
)
14+
15+
func Command() *cobra.Command {
16+
command := &cobra.Command{
17+
Use: "get",
18+
Short: "Get all ploy deployed applications",
19+
Long: "Get all ploy deployed applications",
20+
RunE: func(cmd *cobra.Command, args []string) error {
21+
22+
// Required params
23+
ctx := context.Background()
24+
org := viper.GetString("org")
25+
26+
if org == "" {
27+
return fmt.Errorf("must specify pulumi org via flag or config file")
28+
}
29+
30+
// Generate a random name to use
31+
name := n.GenerateName()
32+
33+
/*
34+
* This is a little hacky, but it works.
35+
* I don't want to have to manage the workspaces myself, so I generate a stack name randomly
36+
*/
37+
stackName := auto.FullyQualifiedStackName(org, "ploy", name)
38+
// Create a stack. We never set the program, we're not going to use it
39+
pulumiStack, err := auto.UpsertStackInlineSource(ctx, stackName, "ploy", nil)
40+
if err != nil {
41+
return fmt.Errorf("failed to create or select stack: %v\n", err)
42+
}
43+
44+
workspace := pulumiStack.Workspace()
45+
46+
// Then we delete the stack from earlier so we don't include it in our list
47+
workspace.RemoveStack(ctx, name)
48+
49+
// List the stacks in our workspace, each stack is an instance of an app
50+
stackList, err := workspace.ListStacks(ctx)
51+
if err != nil {
52+
return fmt.Errorf("failed to list available stacks: %v\n", err)
53+
}
54+
55+
if len(stackList) > 0 {
56+
57+
// Build a pretty table!
58+
table := tablewriter.NewWriter(os.Stdout)
59+
table.SetHeader([]string{"Name", "Last Update", "Deployment Info", "URL"})
60+
61+
// Loop through all the values in the returned stacks and add them to a string array
62+
for _, values := range stackList {
63+
64+
// loop through all the stacks to retrieve the stack outputs
65+
stackName := auto.FullyQualifiedStackName(org, "ploy", values.Name)
66+
stack, err := auto.SelectStack(ctx, stackName, workspace)
67+
if err != nil {
68+
return fmt.Errorf("error selecting stack")
69+
}
70+
out, err := stack.Outputs(ctx)
71+
72+
// add all the values to the output tables
73+
table.Append([]string{values.Name, values.LastUpdate, values.URL, fmt.Sprintf("http://%s", out["address"].Value.(string))})
74+
}
75+
76+
// Render the table to stdout
77+
table.Render()
78+
} else {
79+
log.Info("No ploy apps currently deployed")
80+
}
81+
82+
return nil
83+
},
84+
}
85+
86+
return command
87+
}

0 commit comments

Comments
 (0)