polyn-cli 0.1.1 → 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e6f27c81897f9c1970d3b228466c1f0b5f34b5f6c5c1286064e06ff7106e72e7
4
- data.tar.gz: f4aadba2632bfbc52aa066ac30f627de3a82af17651c1777e4cb4c07275ca967
3
+ metadata.gz: 4ae34f6e95adeb45fa4c421f9eb70960bf599e570a2934365efff5218948d954
4
+ data.tar.gz: f72f8e51082c3d68956bd28a78667d6e484e89c7e90aa87dc212ad6c1d7a59e5
5
5
  SHA512:
6
- metadata.gz: 6dfd33b774a154d24d54ccc63843633343832ca536a0feb69f677b8373bccebd6d4fde76fffccea26e1387644667da904999b1f4058672a89dfd89fd9b44a7ee
7
- data.tar.gz: 5744ef8f8f8db2e72a61b087f96c49db372217aee7964860485d5cb6aec65a4772ffc9a0f19e1e0f9fe2ae2eef0df9e63fa684b2408479e09939f1a2a53af2fe
6
+ metadata.gz: cf5e0ff4dae1a5769da3f041f32a579a159c7fe00281d8a8f563e0c0642d55a3632010f47d7351b6e72592cfd781e6eea1d88091a3a51dd12c8e9045809ca040
7
+ data.tar.gz: 9a91d45f22c7c2ec3d3dce6e32776607d9da6d17d6c93a811c5219e4ae11cf0b47aa60bd0615fb705d857fe1fcd60df3e20243a92c4cfeb98e8170dfa892c0d6
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- polyn-cli (0.1.1)
4
+ polyn-cli (0.1.2)
5
5
  dotenv (~> 2.7.6)
6
6
  json_schemer (~> 0.2)
7
7
  nats-pure (~> 2.0.0)
data/README.md CHANGED
@@ -33,6 +33,7 @@ Run `polyn up` to update your NATS server with the latest configuration in your
33
33
  ## Environment Variables
34
34
 
35
35
  * `NATS_SERVERS` - locations of your servers (defaults to localhost)
36
+ * `NATS_CREDENTIALS` - path to nats credentials file
36
37
  * `POLYN_ENV` - type of environment (defaults to "development")
37
38
 
38
39
  ## Development
@@ -5,11 +5,12 @@ module Polyn
5
5
  ##
6
6
  # Configuration data for Polyn::Cli
7
7
  class Configuration
8
- attr_reader :polyn_env, :nats_servers
8
+ attr_reader :polyn_env, :nats_servers, :nats_credentials
9
9
 
10
10
  def initialize
11
- @polyn_env = ENV["POLYN_ENV"] || "development"
12
- @nats_servers = ENV["NATS_SERVERS"] || "localhost:4222"
11
+ @polyn_env = ENV["POLYN_ENV"] || "development"
12
+ @nats_servers = ENV["NATS_SERVERS"] || "localhost:4222"
13
+ @nats_credentials = ENV["NATS_CREDENTIALS"] || ""
13
14
  end
14
15
  end
15
16
  end
@@ -14,21 +14,32 @@ module Polyn
14
14
 
15
15
  source_root File.join(File.expand_path(__dir__), "../templates")
16
16
 
17
+ def type
18
+ @type ||= event_type.split("/").last
19
+ end
20
+
21
+ def subdir
22
+ @subdir ||= begin
23
+ split = event_type.split("/") - [type]
24
+ split.join("/")
25
+ end
26
+ end
27
+
17
28
  def check_name
18
- Polyn::Cli::Naming.validate_event_type!(event_type)
29
+ Polyn::Cli::Naming.validate_event_type!(type)
19
30
  end
20
31
 
21
32
  def file_name
22
- @file_name ||= "#{event_type}.json"
33
+ @file_name ||= File.join(subdir, "#{type}.json")
23
34
  end
24
35
 
25
36
  def schema_id
26
- Polyn::Cli::Naming.dot_to_colon(event_type)
37
+ Polyn::Cli::Naming.dot_to_colon(type)
27
38
  end
28
39
 
29
40
  def create
30
- say "Creating new schema for #{event_type}"
31
- template "generators/schema.json", File.join(options.dir, "events/#{event_type}.json")
41
+ say "Creating new schema for #{file_name}"
42
+ template "generators/schema.json", File.join(options.dir, "events/#{file_name}")
32
43
  end
33
44
  end
34
45
  end
@@ -41,7 +41,10 @@ module Polyn
41
41
  attr_reader :thor, :events, :client, :bucket, :cloud_event_schema, :events_dir
42
42
 
43
43
  def read_events
44
- Dir.glob(File.join(events_dir, "*.json")).each do |event_file|
44
+ event_files = Dir.glob(File.join(events_dir, "/**/*.json"))
45
+ validate_unique_event_types!(event_files)
46
+
47
+ event_files.each do |event_file|
45
48
  thor.say "Loading 'event #{event_file}'"
46
49
  data_schema = JSON.parse(File.read(event_file))
47
50
  event_type = File.basename(event_file, ".json")
@@ -53,6 +56,30 @@ module Polyn
53
56
  end
54
57
  end
55
58
 
59
+ def validate_unique_event_types!(event_files)
60
+ duplicates = find_duplicates(event_files)
61
+ unless duplicates.empty?
62
+ messages = duplicates.reduce([]) do |memo, (event_type, files)|
63
+ memo << [event_type, *files].join("\n")
64
+ end
65
+ message = [
66
+ "There can only be one of each event type. The following events were duplicated:",
67
+ *messages,
68
+ ].join("\n")
69
+ raise Polyn::Cli::ValidationError, message
70
+ end
71
+ end
72
+
73
+ def find_duplicates(event_files)
74
+ event_types = event_files.group_by do |event_file|
75
+ File.basename(event_file, ".json")
76
+ end
77
+ event_types.each_with_object({}) do |(event_type, files), hash|
78
+ hash[event_type] = files if files.length > 1
79
+ hash
80
+ end
81
+ end
82
+
56
83
  def validate_schema!(event_type, schema)
57
84
  JSONSchemer.schema(schema)
58
85
  rescue StandardError => e
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Polyn
4
4
  class Cli
5
- VERSION = "0.1.1"
5
+ VERSION = "0.1.2"
6
6
  end
7
7
  end
data/lib/polyn/cli.rb CHANGED
@@ -54,8 +54,11 @@ module Polyn
54
54
  directory "tf", File.join(options.dir, "tf")
55
55
  directory "events", File.join(options.dir, "events")
56
56
  template "docker-compose.yml", File.join(options.dir, "docker-compose.yml")
57
- template "gitignore", File.join(options.dir, ".gitignore")
57
+ template "Dockerfile", File.join(options.dir, "Dockerfile")
58
+ template ".dockerignore", File.join(options.dir, ".dockerignore")
59
+ template ".gitignore", File.join(options.dir, ".gitignore")
58
60
  template "README.md", File.join(options.dir, "README.md")
61
+ template "Gemfile", File.join(options.dir, "Gemfile")
59
62
  run tf_init
60
63
  say "Initializing git"
61
64
  inside options.dir do
@@ -67,22 +70,44 @@ module Polyn
67
70
  method_option :dir, default: Dir.getwd
68
71
  desc "tf_init", "Initializes Terraform for configuration"
69
72
  def tf_init
73
+ terraform_root = File.join(options.dir, "tf")
70
74
  say "Initializing Terraform"
71
- inside File.join(options.dir, "tf") do
72
- run "terraform init"
75
+ inside terraform_root do
76
+ # In a development environment we want developers to work with their own local
77
+ # .tfstate rather than one configured in a remote `backend` intended for
78
+ # production use.
79
+ # https://www.terraform.io/language/settings/backends/configuration
80
+ #
81
+ # Terraform assumes only one backend will be configured and there's no path
82
+ # to switch between local and remote. There's also no way to dynamically load
83
+ # modules. https://github.com/hashicorp/terraform/issues/1439
84
+ # Instead we'll copy a backend config to the terraform root if we're in a production
85
+ # environment
86
+ if polyn_env == "production"
87
+ add_remote_backend(terraform_root) { run "terraform init" }
88
+ else
89
+ run "terraform init"
90
+ end
73
91
  end
74
92
  end
75
93
 
76
94
  desc "up", "updates the JetStream streams and consumers, as well the Polyn event registry"
77
95
  def up
78
- if polyn_env == "development"
96
+ terraform_root = File.join(Dir.getwd, "tf")
97
+ # We only want to run nats in the docker container if
98
+ # the developer isn't already running nats themselves locally
99
+ if polyn_env == "development" && !nats_running?
79
100
  say "Starting NATS"
80
101
  run "docker compose up --detach"
81
102
  end
82
103
 
83
104
  say "Updating JetStream configuration"
84
105
  inside "tf" do
85
- run tf_apply
106
+ if polyn_env == "production"
107
+ add_remote_backend(terraform_root) { run tf_apply }
108
+ else
109
+ run tf_apply
110
+ end
86
111
  end
87
112
 
88
113
  say "Updating Polyn event registry"
@@ -99,14 +124,35 @@ module Polyn
99
124
  Polyn::Cli.configuration.nats_servers
100
125
  end
101
126
 
127
+ def nats_credentials
128
+ Polyn::Cli.configuration.nats_credentials
129
+ end
130
+
102
131
  def tf_apply
103
132
  if polyn_env == "development"
104
- %(terraform apply -var "jetstream_servers=#{nats_servers}" -auto-approve)
105
- else
106
133
  %(terraform apply -var "jetstream_servers=#{nats_servers}")
134
+ else
135
+ "terraform apply -auto-approve -input=false "\
136
+ "-var \"jetstream_servers=#{nats_servers}\" "\
137
+ "-var \"nats_credentials=#{nats_credentials}\" " \
138
+ "-var \"polyn_env=production\""
107
139
  end
108
140
  end
109
141
 
142
+ def nats_running?
143
+ # Uses lsof command to look up a process id. Will return `true` if it finds one
144
+ system("lsof -i TCP:4222 -t")
145
+ end
146
+
147
+ def add_remote_backend(tf_root)
148
+ copy_file File.join(tf_root, "remote_state_config/backend.tf"), "backend.tf"
149
+ yield
150
+ # We always want to remove the backend.tf file even if there's an error
151
+ # this way you don't get into a weird state when testing locally
152
+ ensure
153
+ remove_file File.join(tf_root, "backend.tf")
154
+ end
155
+
110
156
  register(Polyn::Cli::SchemaGenerator, "gen:schema", "gen:schema EVENT_TYPE",
111
157
  "Generates a new JSON Schema file for an event")
112
158
  register(Polyn::Cli::StreamGenerator, "gen:stream", "gen:stream NAME",
@@ -0,0 +1,6 @@
1
+ .git
2
+ tf/.terraform
3
+ tf/*.tfstate
4
+ tf/*.backup
5
+ # We don't ignore the terraform lockfile because it ensures that
6
+ # the provider dependency versions are consistent
@@ -0,0 +1,5 @@
1
+ tf/.terraform/**
2
+ *.tfstate
3
+ *.backup
4
+ # We don't ignore the terraform lockfile because it ensures that
5
+ # the provider dependency versions are consistent
@@ -0,0 +1,20 @@
1
+ FROM ruby:3.0.4-alpine3.15 as base
2
+ RUN apk add terraform
3
+ ADD Gemfile* ./
4
+ RUN gem install bundler
5
+ RUN bundle install
6
+
7
+ FROM base as app
8
+ WORKDIR /events
9
+ ADD events ./events
10
+ ADD tf ./tf
11
+
12
+ FROM app as dev
13
+ ENV POLYN_ENV='development'
14
+ RUN bundle exec polyn tf_init
15
+ CMD bundle exec polyn up
16
+
17
+ FROM app as prod
18
+ ENV POLYN_ENV='production'
19
+ RUN bundle exec polyn tf_init
20
+ CMD bundle exec polyn up
@@ -0,0 +1,3 @@
1
+ source "https://rubygems.org"
2
+
3
+ gem "polyn-cli", "~> <%= Polyn::Cli::VERSION %>"
@@ -9,10 +9,19 @@ environment.
9
9
  2. Install Terraform. For M1 Macs, [download the AMD64 version](https://www.terraform.io/downloads)
10
10
  rather than using Homebrew to install Terraform.
11
11
  3. Ensure Docker & Docker Compose is installed
12
- 4. [Install the Polyn CLI]()
13
- 5. Call `polyn up`. By default this will run in `development` mode, which will start the NATS
12
+ 4. [Install the Polyn CLI](https://github.com/SpiffInc/polyn-cli)
13
+ 5. Call `polyn tf_init` if this is the first time using terraform in the codebase.
14
+ 6. Call `polyn up`. By default this will run in `development` mode, which will start the NATS
14
15
  server, configure it via Terraform, and update the Polyn Event Registry.
15
16
 
17
+ ### Running NATS locally
18
+
19
+ `polyn up` will use run a Docker container for you if one is not already running. Alternatively, you can run `nats-server` yourself locally if you prefer.
20
+
21
+ ## Naming Conventions
22
+
23
+ See the Protocol Documentation for [Naming Conventions](https://github.com/SpiffInc/polyn-protocol/blob/main/NAMING_CONVENTIONS.md)
24
+
16
25
  ## Streams
17
26
 
18
27
  Each stream should have its own configuration file under `./tf` . Run `polyn gen:stream <stream_name>` to generate a new configuration file for a stream
@@ -32,6 +41,12 @@ Every schema should be a valid [JSON Schema](https://json-schema.org/) document.
32
41
  The Polyn CLI tool will combine your event schema with the [Cloud Events Schema](https://cloudevents.io/) when it adds it to the Polyn Event Registry.
33
42
  This means you only need to include the JSON Schema for the `data` portion of the Cloud Event and not the entire Cloud Event schema.
34
43
 
44
+ ### Subdirectories
45
+
46
+ If you'd like to organize your events by team ownership or some other convention, you can use subdirectories to do so. The full event type should still be part of the file name. You should also ensure there are not duplicate event types in different directories as only one schema can be defined per event type.
47
+
48
+ You can generate a schema in a subdirectory like this: `polyn gen:schema some/nested/dir/widgets.created.v1`
49
+
35
50
  ## Schema Versioning
36
51
 
37
52
  ### New Event
@@ -49,3 +64,21 @@ Making a change to an event schema that is not backwards-compatible will require
49
64
  json file. The new file should have the same name as your old file, but with the version number increased. Your
50
65
  Producers will need to continue producing both events until you are sure there are no more consumers using the
51
66
  old event.
67
+
68
+ ## Terraform State
69
+
70
+ Terraform generates and maintains a [`terraform.tfstate`](https://www.terraform.io/language/state) file that is used to map terraform configuration to real production server instances. Polyn needs to interact with this file differently based on whether we are developing locally or in a production environment.
71
+
72
+ ### Local Development
73
+
74
+ For local development Polyn expects the `terraform.tfstate` file to exist in the local file system. However, it should not be checked in to version control. We don't want experiments and updates made on a local developer machines to end up as the "source of truth" for our production infrastucture.
75
+
76
+ ### Production
77
+
78
+ In production Terraform recommends keeping `terraform.tfstate` in a [remote storage location](https://www.terraform.io/language/state). The remote state file should be the "source of truth" for your infrastucture and shouldn't be getting accessed during development. Depending on the size of your organization and security policies, not all developers will have access to the remote storage source and you don't want that to prohibit them from adding events, streams, or consumers.
79
+
80
+ Polyn expects you to keep a `./remote_state_config/backend.tf` file that configures a Terraform [backend](https://www.terraform.io/language/settings/backends/configuration). This will only be used when `POLYN_ENV=production`.
81
+
82
+ ## Deployment
83
+
84
+ The default `Dockerfile` generated by [Install the Polyn CLI](https://github.com/SpiffInc/polyn-cli) can help you create an image that will configure terraform and your infrastucture with the latest changes when you execute `docker run`. You'll need to pass in `env` variables for `NATS_SERVERS` and `NATS_CREDENTIALS`. Also any `env` variables needed to connect to your remote state storage.
@@ -0,0 +1,4 @@
1
+ provider "jetstream" {
2
+ servers = var.jetstream_servers
3
+ credentials = var.nats_credentials
4
+ }
@@ -0,0 +1,6 @@
1
+ terraform {
2
+ // Configure a [backend](https://www.terraform.io/language/settings/backends/configuration)
3
+ // to store your `terraform.tfstate` file in for production use
4
+ backend "remote" {
5
+ }
6
+ }
@@ -0,0 +1,15 @@
1
+ variable "jetstream_servers" {
2
+ type = string
3
+ description = "The JetStream servers to connect to"
4
+ }
5
+
6
+ variable "nats_credentials" {
7
+ type = string
8
+ description = "Path to file with NATS credentials"
9
+ }
10
+
11
+ variable "polyn_env" {
12
+ type = string
13
+ description = "The environment terraform is running in"
14
+ default = "development"
15
+ }
@@ -0,0 +1,8 @@
1
+ terraform {
2
+ required_providers {
3
+ jetstream = {
4
+ source = "nats-io/jetstream"
5
+ version = "~> 0.0.31"
6
+ }
7
+ }
8
+ }
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: polyn-cli
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.1.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jarod
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: exe
11
11
  cert_chain: []
12
- date: 2022-08-10 00:00:00.000000000 Z
12
+ date: 2022-08-22 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: dotenv
@@ -102,15 +102,21 @@ files:
102
102
  - lib/polyn/cli/stream_generator.rb
103
103
  - lib/polyn/cli/version.rb
104
104
  - lib/polyn/cloud-event-schema.json
105
+ - lib/polyn/templates/.dockerignore
106
+ - lib/polyn/templates/.gitignore
107
+ - lib/polyn/templates/Dockerfile
108
+ - lib/polyn/templates/Gemfile
105
109
  - lib/polyn/templates/README.md
106
110
  - lib/polyn/templates/docker-compose.yml
107
111
  - lib/polyn/templates/events/.gitkeep
108
112
  - lib/polyn/templates/events/widgets.created.v1.json
109
113
  - lib/polyn/templates/generators/schema.json
110
114
  - lib/polyn/templates/generators/stream.tf
111
- - lib/polyn/templates/gitignore
112
- - lib/polyn/templates/tf/event_registry.tf
113
- - lib/polyn/templates/tf/main.tf
115
+ - lib/polyn/templates/tf/kv_buckets.tf
116
+ - lib/polyn/templates/tf/provider.tf
117
+ - lib/polyn/templates/tf/remote_state_config/backend.tf
118
+ - lib/polyn/templates/tf/variables.tf
119
+ - lib/polyn/templates/tf/versions.tf
114
120
  - lib/polyn/templates/tf/widgets.tf
115
121
  - polyn-cli.gemspec
116
122
  homepage: https://github.com/Spiffinc/polyn-cli
@@ -1,3 +0,0 @@
1
- tf/.terraform/**
2
- *.tfstate
3
- .terraform.lock.hcl
@@ -1,17 +0,0 @@
1
- terraform {
2
- required_providers {
3
- jetstream = {
4
- source = "nats-io/jetstream"
5
- }
6
- }
7
-
8
- }
9
-
10
- variable "jetstream_servers" {
11
- type = string
12
- description = "The JetStream servers to connect to"
13
- }
14
-
15
- provider "jetstream" {
16
- servers = var.jetstream_servers
17
- }