hephaestus 0.6.4 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +58 -22
- data/README.md +169 -6
- data/bin/hephaestus +20 -3
- data/lib/Rakefile +10 -0
- data/lib/hephaestus/engine.rb +72 -0
- data/lib/hephaestus/http.rb +36 -0
- data/lib/hephaestus/middleware/malformed_request.rb +100 -0
- data/lib/hephaestus/middleware/openapi_validation.rb +89 -0
- data/lib/hephaestus/middleware/tracing_attributes.rb +50 -0
- data/lib/hephaestus/middleware.rb +7 -0
- data/lib/hephaestus/support/hephaestus/api.rb +105 -0
- data/lib/hephaestus/support/hephaestus/webmocks/browser_webmock.rb +21 -0
- data/lib/hephaestus/support/hephaestus/webmocks/slack_webmock.rb +21 -0
- data/lib/hephaestus/support/hephaestus/webmocks/yetto_webmock.rb +195 -0
- data/lib/hephaestus/support/rails.rb +35 -0
- data/lib/hephaestus/test_helper.rb +45 -0
- data/lib/hephaestus/version.rb +2 -2
- data/lib/hephaestus.rb +1 -0
- data/lib/tasks/hephaestus_tasks.rake +7 -0
- data/lib/tasks/package.rake +9 -0
- data/lib/tasks/rubocop.rake +6 -0
- data/lib/version.rb +6 -0
- data/templates/app/controllers/app_controller.rb +1 -0
- data/templates/app/controllers/application_controller.rb +2 -101
- data/templates/app/jobs/update_yetto_job.rb +1 -1
- data/templates/app/services/yetto_service.rb +2 -2
- data/templates/script/hmac_text +1 -1
- data/templates/test/jobs/update_yetto_job_test.rb +1 -1
- data/templates/test/support/webmocks/yetto_webmock.rb +5 -5
- metadata +330 -12
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3f7de19c3d9ce15fc3a7851c0ca74e88202a62c8fcbf46cffb13131df112f384
|
4
|
+
data.tar.gz: bdd11f809c9c38d8c8b193a00c99ffce4a0dce847da1766a9f0ec371e932e359
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 19f8a930236eefd1fc9b6b6c0c612e314c103f5e2482aadf331543b8750b5b21df2de2b43c0b1dc02425eaaeba6cdd6f8f76e525626b94226e9eb85ac6cd6b16
|
7
|
+
data.tar.gz: 0cab0d8f9ae2fdeff83f5a85af55b65917d375c1937cdea374276c9005d6b471f3ae8b6c08e4e1c45181095722c51f13895d079628a77cc8061eb7e685fa358f
|
data/CHANGELOG.md
CHANGED
@@ -1,89 +1,129 @@
|
|
1
|
+
# [v0.7.0] - 15-11-2024
|
2
|
+
## What's Changed
|
3
|
+
* Run as an engine by @gjtorikian in https://github.com/yettoapp/hephaestus/pull/25
|
4
|
+
|
5
|
+
|
6
|
+
**Full Changelog**: https://github.com/yettoapp/hephaestus/compare/v0.6.4...v0.7.0
|
1
7
|
# [v0.6.4] - 09-07-2024
|
8
|
+
|
2
9
|
## What's Changed
|
3
|
-
* Updates for OpenAPI dependency by @gjtorikian in https://github.com/yettoapp/hephaestus/pull/23
|
4
10
|
|
11
|
+
- Updates for OpenAPI dependency by @gjtorikian in https://github.com/yettoapp/hephaestus/pull/23
|
5
12
|
|
6
13
|
**Full Changelog**: https://github.com/yettoapp/hephaestus/compare/v0.6.3...v0.6.4
|
14
|
+
|
7
15
|
# [v0.6.3] - 05-07-2024
|
16
|
+
|
8
17
|
## What's Changed
|
9
|
-
* Changes to support Rack 3 param parsing by @gjtorikian in https://github.com/yettoapp/hephaestus/pull/21
|
10
18
|
|
19
|
+
- Changes to support Rack 3 param parsing by @gjtorikian in https://github.com/yettoapp/hephaestus/pull/21
|
11
20
|
|
12
21
|
**Full Changelog**: https://github.com/yettoapp/hephaestus/compare/v0.6.2...v0.6.3
|
22
|
+
|
13
23
|
# [v0.6.2] - 19-06-2024
|
24
|
+
|
14
25
|
## What's Changed
|
15
|
-
* Use Ruby test suite by @gjtorikian in https://github.com/yettoapp/hephaestus/pull/18
|
16
|
-
* Catch up with plug updates by @balevine in https://github.com/yettoapp/hephaestus/pull/17
|
17
26
|
|
27
|
+
- Use Ruby test suite by @gjtorikian in https://github.com/yettoapp/hephaestus/pull/18
|
28
|
+
- Catch up with plug updates by @balevine in https://github.com/yettoapp/hephaestus/pull/17
|
18
29
|
|
19
30
|
**Full Changelog**: https://github.com/yettoapp/hephaestus/compare/v0.6.1...v0.6.2
|
31
|
+
|
20
32
|
## [v0.6.1] - 12-02-2024
|
33
|
+
|
21
34
|
## What's Changed
|
22
|
-
* Port recent Ruby, TOML, and OpenAPI changes by @gjtorikian in https://github.com/yettoapp/hephaestus/pull/16
|
23
35
|
|
36
|
+
- Port recent Ruby, TOML, and OpenAPI changes by @gjtorikian in https://github.com/yettoapp/hephaestus/pull/16
|
24
37
|
|
25
38
|
**Full Changelog**: https://github.com/yettoapp/hephaestus/compare/v0.6.0...v0.6.1
|
39
|
+
|
26
40
|
## [v0.6.0] - 19-12-2023
|
41
|
+
|
27
42
|
## What's Changed
|
28
|
-
* Remove switch logic from plugs by @gjtorikian in https://github.com/yettoapp/hephaestus/pull/15
|
29
43
|
|
44
|
+
- Remove switch logic from plugs by @gjtorikian in https://github.com/yettoapp/hephaestus/pull/15
|
30
45
|
|
31
46
|
**Full Changelog**: https://github.com/yettoapp/hephaestus/compare/v0.5.2...v0.6.0
|
47
|
+
|
32
48
|
## [v0.5.2] - 13-11-2023
|
49
|
+
|
33
50
|
## What's Changed
|
34
|
-
* Fix RAILS_ENV in fly-staging.toml by @balevine in https://github.com/yettoapp/hephaestus/pull/13
|
35
|
-
* Change dependabot checking to by @gjtorikian in https://github.com/yettoapp/hephaestus/pull/14
|
36
51
|
|
52
|
+
- Fix RAILS_ENV in fly-staging.toml by @balevine in https://github.com/yettoapp/hephaestus/pull/13
|
53
|
+
- Change dependabot checking to by @gjtorikian in https://github.com/yettoapp/hephaestus/pull/14
|
37
54
|
|
38
55
|
**Full Changelog**: https://github.com/yettoapp/hephaestus/compare/v0.5.1...v0.5.2
|
56
|
+
|
39
57
|
## [v0.5.1] - 31-10-2023
|
58
|
+
|
40
59
|
## What's Changed
|
41
|
-
|
60
|
+
|
61
|
+
- Remove excess :// characters from PROTOCOL by @balevine in https://github.com/yettoapp/hephaestus/pull/12
|
42
62
|
|
43
63
|
## New Contributors
|
44
|
-
|
64
|
+
|
65
|
+
- @balevine made their first contribution in https://github.com/yettoapp/hephaestus/pull/12
|
45
66
|
|
46
67
|
**Full Changelog**: https://github.com/yettoapp/hephaestus/compare/v0.5.0...v0.5.1
|
68
|
+
|
47
69
|
## [v0.5.0] - 13-10-2023
|
70
|
+
|
48
71
|
## What's Changed
|
49
|
-
* More edits by @gjtorikian in https://github.com/yettoapp/hephaestus/pull/11
|
50
72
|
|
73
|
+
- More edits by @gjtorikian in https://github.com/yettoapp/hephaestus/pull/11
|
51
74
|
|
52
75
|
**Full Changelog**: https://github.com/yettoapp/hephaestus/compare/v0.4.0...v0.5.0
|
76
|
+
|
53
77
|
## [v0.4.0] - 29-09-2023
|
78
|
+
|
54
79
|
## What's Changed
|
55
|
-
* Yet more changes by @gjtorikian in https://github.com/yettoapp/hephaestus/pull/10
|
56
80
|
|
81
|
+
- Yet more changes by @gjtorikian in https://github.com/yettoapp/hephaestus/pull/10
|
57
82
|
|
58
83
|
**Full Changelog**: https://github.com/yettoapp/hephaestus/compare/v0.3.1...v0.4.0
|
84
|
+
|
59
85
|
## [v0.3.1] - 18-09-2023
|
86
|
+
|
60
87
|
**Full Changelog**: https://github.com/yettoapp/hephaestus/compare/v0.3.0...v0.3.1
|
88
|
+
|
61
89
|
## [v0.3.0] - 12-09-2023
|
90
|
+
|
62
91
|
## What's Changed
|
63
|
-
|
64
|
-
|
92
|
+
|
93
|
+
- Bump actions/checkout from 3 to 4 by @dependabot in https://github.com/yettoapp/hephaestus/pull/8
|
94
|
+
- Use centralized workflows for testing by @gjtorikian in https://github.com/yettoapp/hephaestus/pull/9
|
65
95
|
|
66
96
|
## New Contributors
|
67
|
-
|
97
|
+
|
98
|
+
- @dependabot made their first contribution in https://github.com/yettoapp/hephaestus/pull/8
|
68
99
|
|
69
100
|
**Full Changelog**: https://github.com/yettoapp/hephaestus/compare/v0.2.3...v0.3.0
|
101
|
+
|
70
102
|
## [v0.2.3] - 21-08-2023
|
103
|
+
|
71
104
|
## What's Changed
|
72
|
-
* Updates from staging by @gjtorikian in https://github.com/yettoapp/hephaestus/pull/7
|
73
105
|
|
106
|
+
- Updates from staging by @gjtorikian in https://github.com/yettoapp/hephaestus/pull/7
|
74
107
|
|
75
108
|
**Full Changelog**: https://github.com/yettoapp/hephaestus/compare/v0.2.2...v0.2.3
|
109
|
+
|
76
110
|
## [v0.2.2] - 21-08-2023
|
111
|
+
|
77
112
|
## What's Changed
|
78
|
-
* 404/500 error handling by @gjtorikian in https://github.com/yettoapp/hephaestus/pull/5
|
79
|
-
* Parse OpenAPI schema *once* by @gjtorikian in https://github.com/yettoapp/hephaestus/pull/6
|
80
113
|
|
114
|
+
- 404/500 error handling by @gjtorikian in https://github.com/yettoapp/hephaestus/pull/5
|
115
|
+
- Parse OpenAPI schema _once_ by @gjtorikian in https://github.com/yettoapp/hephaestus/pull/6
|
81
116
|
|
82
117
|
**Full Changelog**: https://github.com/yettoapp/hephaestus/compare/v0.2.1...v0.2.2
|
118
|
+
|
83
119
|
## [v0.2.1] - 11-07-2023
|
120
|
+
|
84
121
|
null
|
122
|
+
|
85
123
|
## [v0.2.0] - 11-07-2023
|
124
|
+
|
86
125
|
null
|
126
|
+
|
87
127
|
# Changelog
|
88
128
|
|
89
129
|
## [v0.1.3](https://github.com/yettoapp/hephaestus/tree/v0.1.3) (2023-03-15)
|
@@ -117,7 +157,3 @@ null
|
|
117
157
|
**Merged pull requests:**
|
118
158
|
|
119
159
|
- Add a Rails application template for plugs [\#1](https://github.com/yettoapp/hephaestus/pull/1) ([gjtorikian](https://github.com/gjtorikian))
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
\* *This Changelog was automatically generated by [github_changelog_generator](https://github.com/github-changelog-generator/github-changelog-generator)*
|
data/README.md
CHANGED
@@ -1,18 +1,29 @@
|
|
1
1
|
# Hephaestus
|
2
2
|
|
3
|
-
A plug template for Yetto.
|
3
|
+
A plug template for Yetto. It serves two purposes:
|
4
|
+
|
5
|
+
1. Use this to quickly spin up new plugs with a set of defaults.
|
6
|
+
2. Hephaestus also exists as an engine, and is responsible for the majority of a plug's Yetto communication logic.
|
4
7
|
|
5
8
|
## Usage
|
6
9
|
|
10
|
+
First, install Hephaestus _globally_, on your system:
|
11
|
+
|
7
12
|
```
|
8
13
|
gem install hephaestus
|
14
|
+
```
|
9
15
|
|
16
|
+
Then, in the parent directory where you want your plug to be created, run:
|
17
|
+
|
18
|
+
```
|
10
19
|
hephaestus plug-app
|
11
20
|
```
|
12
21
|
|
13
|
-
Where `app` represents the name of the platform you'd like to interact with `jira`, `notion`, `slack`, etc.
|
22
|
+
Where `app` represents the name of the platform you'd like to interact with, like `jira`, `notion`, `slack`, etc.
|
14
23
|
|
15
|
-
|
24
|
+
### Testing locally
|
25
|
+
|
26
|
+
If you're working on updating/testing this gem locally, you may also try:
|
16
27
|
|
17
28
|
```
|
18
29
|
rm -rf plug-app && DEBUG=1 hephaestus/bin/hephaestus plug-app
|
@@ -20,11 +31,145 @@ rm -rf plug-app && DEBUG=1 hephaestus/bin/hephaestus plug-app
|
|
20
31
|
|
21
32
|
This way you can wipe the dir and quickly iterate on new changes.
|
22
33
|
|
23
|
-
## Building upon
|
34
|
+
## Building upon the base
|
35
|
+
|
36
|
+
Most of the files which Hephaestus creates for you shouldn't need much modification. The information below is part-informative, part-guidelines on how to extend the generated code for your specific plug.
|
37
|
+
|
38
|
+
### Controllers
|
39
|
+
|
40
|
+
#### `ApplicationController`
|
41
|
+
|
42
|
+
Your application controller inherits from `Hephaestus::ApplicationController`, and handles the basics of request/response cycles. Two common callbacks to add here are:
|
43
|
+
|
44
|
+
- `before_action :set_request_span_context`, which lets you add data to the `OpenTelemetry` trace before a request occurs
|
45
|
+
- `after_action :set_response_span_context`, similarly, lets you add to the trace after the response is sent
|
46
|
+
|
47
|
+
For examples on how to integrate with these, check out `plug-email`.
|
48
|
+
|
49
|
+
#### `SettingsController`
|
50
|
+
|
51
|
+
Typically, your settings inherit directly from Hephaestus. We'll discuss more about how this works in `routes.rb`. Either way, you'll define two files:
|
52
|
+
|
53
|
+
- `app/views/settings/new.json.jbuilder`
|
54
|
+
- `app/views/settings/edit.json.jbuilder`
|
55
|
+
|
56
|
+
Define the settings UI in these files. See `plug-email` for an example of a settings page with no customizations.
|
57
|
+
|
58
|
+
However, for multi-step plugs, it's common to expect customized steps. If you'd like to extend the settings controller, create a file with one of two methods:
|
59
|
+
|
60
|
+
- `new`
|
61
|
+
- `edit`
|
62
|
+
|
63
|
+
You only need to define these methods if you're actually overriding the action—often, this only means `edit`. If you do define methods in this controller, remember to add `before_action :ensure_json_request` at the top. For examples on settings pages which have _some_ customization, see `plug-github` and `plug-linear`. For an example of a totally custom settings page, see `plug-zendesk`.
|
64
|
+
|
65
|
+
#### `AppController`
|
66
|
+
|
67
|
+
The logic to communicate with your platform sits in `AppController`. Every plug is unique, so the requirements for your platform varies!
|
68
|
+
|
69
|
+
At a minimum, though, you should place all the logic which authenticates requests in the `Authable` concern. See `plug-github` and `plug-linear` for practical examples.
|
70
|
+
|
71
|
+
#### `YettoController`
|
72
|
+
|
73
|
+
Place the logic which receives webhooks from Yetto in the `YettoController`. This controller must `include Hephaestus::ValidatesFromYetto`, and must define `before_action :from_yetto?`. Beyond that, you can respond to events as they come in, and fire jobs to perform the actions your platform needs to integrate with Yetto.
|
74
|
+
|
75
|
+
### Jobs
|
76
|
+
|
77
|
+
Jobs are at the heart of plugs, because they perform time-sensitive work asynchronously. Since Hephaestus already creates an `ApplicationJob` which inherits from `Hephaestus::ApplicationJob`, there isn't much else you need to do, other than define your jobs.
|
78
|
+
|
79
|
+
Whenever you need to integrate with Yetto, just pass the appropriate arguments to `Hephaestus::UpdateYettoJob`.
|
80
|
+
|
81
|
+
The bulk of your plug logic should exist within these jobs. Keep the controllers as place to authenticate incoming requests and respond with appropriate status codes.
|
82
|
+
|
83
|
+
### Constants
|
84
|
+
|
85
|
+
Place any and all constants in the file called `lib/constants.rb`.
|
86
|
+
|
87
|
+
Note that Hepheastus provides several convenience methods and constants for you. These are:
|
88
|
+
|
89
|
+
- `plug_shortname`: the downcased version of the plug (e.g., `Slack => slack`, `GitHub => github`)
|
90
|
+
- `plug_name`: The proper name of the plug (e.g., `Slack`, `GitHub`)
|
91
|
+
- `plug_url`: The plug's URL (e.g., `email{.plugs.yetto.app,.plugs.yetto.dev,.ngrok.io}`)
|
92
|
+
|
93
|
+
### Application Configuration
|
94
|
+
|
95
|
+
The typical Rails' environment configs (`config/environments/{development.rb,test.rb,production.rb}`) are managed by Hephaestus. However, any plug can define their own environment files as well. These are added after Hephaestus' default configuration.
|
96
|
+
|
97
|
+
The `test.rb` file in `plug-email` provides an example of this.
|
98
|
+
|
99
|
+
#### Upgrading Rails
|
100
|
+
|
101
|
+
Upgrading Rails always introduces breaking changes, even between minor releases. To ensure that a plug still works after a Rails upgrade, first, add
|
102
|
+
|
103
|
+
```ruby
|
104
|
+
rails "x"
|
105
|
+
```
|
106
|
+
|
107
|
+
to your plug's Gemfile (where `"x"` is the new Rails version.) Note that this means plugs can run Rails versions independently of what Hephaestus provides (although variance is not recommended, it can help during the upgrade process).
|
108
|
+
|
109
|
+
Next, call `rails app:update:configs`. You can pretty much ignore every changed file, except the one that starts with `new_framework_defaults_`. Use these values to slowly reintroduce the new configuration changes, as described in [the Rails documentation](https://guides.rubyonrails.org/v8.0/upgrading_ruby_on_rails.html#configure-framework-defaults).
|
110
|
+
|
111
|
+
### Integrating the Plug with the Engine
|
112
|
+
|
113
|
+
There are certain times where the child application needs to "inject" data into Hephaestus. These are typically customizations that are unique to the plug. Place these customizations in `config/application.rb`, inside of an `initializer :engines_blank_point do` block.
|
114
|
+
|
115
|
+
At a minimum, you'll need to define your plug's API version, and the version of the Yetto API it's communicating with:
|
116
|
+
|
117
|
+
```ruby
|
118
|
+
initializer :engines_blank_point do
|
119
|
+
config.yetto_api_version = "2023-03-06"
|
120
|
+
config.plug_api_version = "2023-03-06"
|
121
|
+
end
|
122
|
+
```
|
123
|
+
|
124
|
+
You can also define several other customizations:
|
125
|
+
|
126
|
+
- `config.tracing_body_filters`: use this to scrub out any sensitive data coming in from your platform, to prevent it from leaking in our metrics aggregator. See the override in `plug-email` for an example. The default is to simply log everything (after running it through [Rails' `ParameterFilter`](https://api.rubyonrails.org/v7.1/classes/ActiveSupport/ParameterFilter.html), of course), which may not be a good idea!
|
127
|
+
- `config.enhance_update_yetto_job`: use this to include any plug-specific logic which needs to occur as part of issuing calls to the Yetto API
|
128
|
+
|
129
|
+
For an example of both these customizations, see `plug-email`.
|
130
|
+
|
131
|
+
There's also middleware you can use to protect any endpoint with an OpenAPI schema. To do so, add:
|
132
|
+
|
133
|
+
```ruby
|
134
|
+
require "hephaestus/middleware/openapi_validation"
|
135
|
+
config.middleware.use(Hephaestus::Middleware::OpenapiValidation, match_path: "/app/2023-03-06/path/", limit_methods_to: ["POST"], spec: Rails.root.join("lib/schemas/path/2023-03-06/openapi.json"))
|
136
|
+
```
|
137
|
+
|
138
|
+
In this example, any `POST` to `match_path` is protected by the OpenAPI `spec` provided.
|
139
|
+
|
140
|
+
### Services
|
141
|
+
|
142
|
+
Place any HTTP communication necessary to communicate with your platform in this directory. All other code should which communicates to your plug should rely on methods defined in this file.
|
143
|
+
|
144
|
+
See any of the plugs for an example of how to define this interaction, particularly with `httpsensible`.
|
145
|
+
|
146
|
+
### Routes
|
147
|
+
|
148
|
+
If you do not have any custom actions to perform in the settings controller, define your settings pages as:
|
149
|
+
|
150
|
+
```ruby
|
151
|
+
# you must always include this line when referring to `hephaestus_settings`
|
152
|
+
HephaestusSettingsController = Hephaestus::SettingsController
|
153
|
+
|
154
|
+
get "/api/#{Rails.application.config.plug_api_version}/settings", to: "hephaestus_settings#new"
|
155
|
+
get "/api/#{Rails.application.config.plug_api_version}/settings/:organization_id/:inbox_id/:plug_installation_id/edit", to: "hephaestus_settings#edit"
|
156
|
+
```
|
157
|
+
|
158
|
+
Otherwise, be sure to refer to `hephaestus_settings` only for any default settings related actions:
|
159
|
+
|
160
|
+
```ruby
|
161
|
+
# you must always include this line when referring to `hephaestus_settings`
|
162
|
+
HephaestusSettingsController = Hephaestus::SettingsController
|
163
|
+
|
164
|
+
get "/api/#{Rails.application.config.plug_api_version}/settings", to: "hephaestus_settings#new"
|
165
|
+
get "/api/#{Rails.application.config.plug_api_version}/settings/:organization_id/:inbox_id/:plug_installation_id/edit", to: "settings#edit"
|
166
|
+
```
|
167
|
+
|
168
|
+
Otherwise, you can add any other routes your plug needs to `config/routes.rb`.
|
24
169
|
|
25
170
|
### Launching the server
|
26
171
|
|
27
|
-
First, you'll note that you have a `script/ngrok` file, which launches an ngrok server at `https
|
172
|
+
First, you'll note that you have a `script/ngrok` file, which launches an ngrok server at `https://${hostname}-plug-app.ngrok.io`, which maps locally to `http://localhost:6661`. Setting up ngrok can be essential when testing the platform locally for the first time. (Keep in mind that you still need to run `script/server` to actually start the local server—this is just to help facilitate communication with the platform.)
|
28
173
|
|
29
174
|
### Setting up routes
|
30
175
|
|
@@ -42,6 +187,24 @@ After a user submits a plug installation on the Yetto side, it'll send a POST pa
|
|
42
187
|
|
43
188
|
Any code which communicates with the third party should be placed in the `app/services` directory. A generic HTTP service is included.
|
44
189
|
|
190
|
+
### Writing tests
|
191
|
+
|
192
|
+
In addition to writing tests for your plug interacting with its platform, many of your tests will also need to cover your plug's interaction with Yetto.
|
193
|
+
|
194
|
+
Writing these tests well comes with time; it's not an easy thing to cover in a pithy paragraph. Regardless, here's a bit of hopefully helpful advice.
|
195
|
+
|
196
|
+
Be sure to tests requests before _and_ after they occur. Typically, this takes the form of a `stub_request` and `assert_requested`. Similarly, be sure to:
|
197
|
+
|
198
|
+
- `include Hephaestus::Webmocks::SlackWebmock` whenever a plug action is expected to error out to Slack
|
199
|
+
- `include Hephaestus::Webmocks::YettoWebmock` whenever a plug needs to issue a request to Yetto
|
200
|
+
|
201
|
+
Use `include Hephaestus::API::TestHelpers` whenever you need to write a test which either
|
202
|
+
|
203
|
+
- match `"/#{plug_shortname}` routes (by issuing calls to `plug`). These are routes which your external server is calling into.
|
204
|
+
- match `/api` routes (by issuing calls to `api`). These are routes which Yetto is calling into.
|
205
|
+
|
206
|
+
For examples on when and how to use these methods, see `plug-github`.
|
207
|
+
|
45
208
|
## Acknowledgements
|
46
209
|
|
47
|
-
|
210
|
+
The template generation for this project was heavily based on [thoughtbot/suspenders](https://github.com/thoughtbot/suspenders).
|
data/bin/hephaestus
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
4
|
require "pathname"
|
5
|
-
require
|
5
|
+
require "rainbow"
|
6
6
|
|
7
7
|
source_path = (Pathname.new(__FILE__).dirname + "../lib").expand_path
|
8
8
|
$LOAD_PATH << source_path
|
@@ -34,7 +34,24 @@ if (str = ARGV.first)
|
|
34
34
|
end
|
35
35
|
end
|
36
36
|
|
37
|
-
|
37
|
+
puts Rainbow("Checking to see if Hephaestus is the latest and greatest...").green
|
38
|
+
require "net/http"
|
39
|
+
begin
|
40
|
+
Net::HTTP.get(URI("https://www.yetto.app"))
|
41
|
+
rescue Socket::ResolutionError
|
42
|
+
puts Rainbow("Unable to check for updates. Are you connected to the internet?").red
|
43
|
+
exit(1)
|
44
|
+
end
|
45
|
+
|
46
|
+
%x(gem outdated).split("\n").each do |line|
|
47
|
+
if line.include?("hephaestus")
|
48
|
+
puts Rainbow("There is a new version of Hephaestus available (`#{line}`). Please run 'gem update hephaestus' to update.").red
|
49
|
+
exit(1)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
require "rails"
|
54
|
+
require "hephaestus/engine"
|
38
55
|
|
39
56
|
if ARGV.empty?
|
40
57
|
puts "Please provide a path for the new application"
|
@@ -57,7 +74,7 @@ unless path.start_with?("plug-")
|
|
57
74
|
exit 1
|
58
75
|
end
|
59
76
|
|
60
|
-
|
77
|
+
if path.split("-").length < 2
|
61
78
|
puts Rainbow("\nError: The directory name must start with 'plug-'").red
|
62
79
|
exit 1
|
63
80
|
end
|
data/lib/Rakefile
ADDED
@@ -0,0 +1,72 @@
|
|
1
|
+
# typed: false
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require "propshaft"
|
5
|
+
require "jbuilder"
|
6
|
+
require "solid_queue"
|
7
|
+
require "mission_control/jobs"
|
8
|
+
|
9
|
+
module Hephaestus
|
10
|
+
class Engine < ::Rails::Engine
|
11
|
+
isolate_namespace Hephaestus
|
12
|
+
config.generators.api_only = true
|
13
|
+
|
14
|
+
GIT_SHA = case Rails.env
|
15
|
+
when "production", "staging"
|
16
|
+
result = nil
|
17
|
+
result = "" if PRECOMPILING # skip if we're just precompiling assets
|
18
|
+
|
19
|
+
result ||= Rails.root.join("GIT_SHA").read if Rails.root.join("GIT_SHA").exist?
|
20
|
+
|
21
|
+
result ||= %x(git rev-parse HEAD) # fallback to git (should only happen locally)
|
22
|
+
|
23
|
+
result || ""
|
24
|
+
when "development"
|
25
|
+
%x(git rev-parse HEAD)
|
26
|
+
when "test"
|
27
|
+
"deadbeef"
|
28
|
+
end.chomp
|
29
|
+
|
30
|
+
require "hephaestus/http"
|
31
|
+
|
32
|
+
initializer "hephaestus.add_middleware" do |app|
|
33
|
+
Hephaestus::Engine.root.glob("lib/hephaestus/middleware/*.{rb}").each { |file| require_relative file }
|
34
|
+
|
35
|
+
app.config.middleware.insert(0, Hephaestus::Middleware::TracingAttributes)
|
36
|
+
app.config.middleware.insert(0, Hephaestus::Middleware::MalformedRequest)
|
37
|
+
|
38
|
+
app.config.middleware.use(Hephaestus::Middleware::OpenapiValidation)
|
39
|
+
end
|
40
|
+
|
41
|
+
initializer :append_migrations do |app|
|
42
|
+
unless app.root.to_s.match?(root.to_s)
|
43
|
+
config.paths["db/migrate"].expanded.each do |expanded_path|
|
44
|
+
app.config.paths["db/migrate"] << expanded_path
|
45
|
+
ActiveRecord::Migrator.migrations_paths << expanded_path
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
class << self
|
51
|
+
def insert_routes
|
52
|
+
Rails.application.routes.draw do
|
53
|
+
# Staff pages
|
54
|
+
get("staff", to: "staff#index")
|
55
|
+
|
56
|
+
constraints(->(request) { StaffController.staff_request?(request) }) do
|
57
|
+
mount(MissionControl::Jobs::Engine, at: "staff/jobs", as: :staff_jobs)
|
58
|
+
end
|
59
|
+
|
60
|
+
#############################################
|
61
|
+
# error pages -- these MUST be at the end! ##
|
62
|
+
#############################################
|
63
|
+
|
64
|
+
get("/500", to: "application#render500") if Rails.env.production? || Rails.env.staging?
|
65
|
+
|
66
|
+
match("/", to: "application#not_found", via: :all)
|
67
|
+
match("/*unmatched_route", to: "application#not_found", via: :all)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module Hephaestus
|
5
|
+
module HTTP
|
6
|
+
OK = "OK"
|
7
|
+
OK_I = 200
|
8
|
+
|
9
|
+
CREATED = "Created"
|
10
|
+
CREATED_I = 201
|
11
|
+
|
12
|
+
NO_CONTENT = "No Content"
|
13
|
+
NO_CONTENT_I = 204
|
14
|
+
|
15
|
+
FOUND = "Found"
|
16
|
+
FOUND_I = 302
|
17
|
+
|
18
|
+
BAD_REQUEST = "Bad Request"
|
19
|
+
BAD_REQUEST_I = 400
|
20
|
+
|
21
|
+
UNAUTHORIZED = "Unauthorized"
|
22
|
+
UNAUTHORIZED_I = 401
|
23
|
+
|
24
|
+
FORBIDDEN = "Forbidden"
|
25
|
+
FORBIDDEN_I = 403
|
26
|
+
|
27
|
+
NOT_FOUND = "Not Found"
|
28
|
+
NOT_FOUND_I = 404
|
29
|
+
|
30
|
+
NOT_ACCEPTABLE = "Not Acceptable"
|
31
|
+
NOT_ACCEPTABLE_I = 406
|
32
|
+
|
33
|
+
SERVICE_UNAVAILABLE = "Service Unavailable"
|
34
|
+
SERVICE_UNAVAILABLE_I = 503
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,100 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module Hephaestus
|
5
|
+
module Middleware
|
6
|
+
# There is no valid reason for a request to contain a malformed string
|
7
|
+
# so just return HTTP 400 (Bad Request) if we receive one
|
8
|
+
class MalformedRequest
|
9
|
+
include ActionController::HttpAuthentication::Basic
|
10
|
+
|
11
|
+
NULL_BYTE_REGEX = Regexp.new(Regexp.escape("\u0000")).freeze
|
12
|
+
|
13
|
+
attr_reader :app
|
14
|
+
|
15
|
+
def initialize(app)
|
16
|
+
@app = app
|
17
|
+
end
|
18
|
+
|
19
|
+
def call(env)
|
20
|
+
return [Hephaestus::HTTP::BAD_REQUEST_I, { "Content-Type" => "text/plain" }, [Hephaestus::HTTP::BAD_REQUEST]] if request_contains_malformed_string?(env)
|
21
|
+
|
22
|
+
app.call(env)
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def request_contains_malformed_string?(env)
|
28
|
+
# Duplicate the env, so it is not modified when accessing the parameters
|
29
|
+
# https://github.com/rails/rails/blob/34991a6ae2fc68347c01ea7382fa89004159e019/actionpack/lib/action_dispatch/http/parameters.rb#L59
|
30
|
+
request = ActionDispatch::Request.new(env.dup)
|
31
|
+
|
32
|
+
return true if malformed_path?(request.path)
|
33
|
+
return true if credentials_malformed?(request)
|
34
|
+
|
35
|
+
request.params.values.any? do |value|
|
36
|
+
param_has_null_byte?(value)
|
37
|
+
end
|
38
|
+
rescue ActionController::BadRequest
|
39
|
+
# If we can't build an ActionDispatch::Request something's wrong
|
40
|
+
# This would also happen if `#params` contains invalid UTF-8
|
41
|
+
# in this case we'll return a 400
|
42
|
+
true
|
43
|
+
end
|
44
|
+
|
45
|
+
def malformed_path?(path)
|
46
|
+
string_malformed?(Rack::Utils.unescape(path))
|
47
|
+
rescue ArgumentError
|
48
|
+
# Rack::Utils.unescape raised this, path is malformed.
|
49
|
+
true
|
50
|
+
end
|
51
|
+
|
52
|
+
def credentials_malformed?(request)
|
53
|
+
credentials = if has_basic_credentials?(request)
|
54
|
+
decode_credentials(request).presence
|
55
|
+
else
|
56
|
+
request.authorization.presence
|
57
|
+
end
|
58
|
+
|
59
|
+
return false unless credentials
|
60
|
+
|
61
|
+
string_malformed?(credentials)
|
62
|
+
end
|
63
|
+
|
64
|
+
def param_has_null_byte?(value, depth = 0)
|
65
|
+
# Guard against possible attack sending large amounts of nested params
|
66
|
+
# Should be safe as deeply nested params are highly uncommon.
|
67
|
+
return false if depth > 2
|
68
|
+
|
69
|
+
depth += 1
|
70
|
+
|
71
|
+
if value.respond_to?(:match)
|
72
|
+
string_malformed?(value)
|
73
|
+
elsif value.respond_to?(:values)
|
74
|
+
value.values.any? do |hash_value|
|
75
|
+
param_has_null_byte?(hash_value, depth)
|
76
|
+
end
|
77
|
+
elsif value.is_a?(Array)
|
78
|
+
value.any? do |array_value|
|
79
|
+
param_has_null_byte?(array_value, depth)
|
80
|
+
end
|
81
|
+
else
|
82
|
+
false
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def string_malformed?(string)
|
87
|
+
# We're using match instead of include because that raises an ArgumentError
|
88
|
+
# when the string contains invalid UTF-8
|
89
|
+
#
|
90
|
+
# We try to encode the string from ASCII-8BIT to UTF8. If we failed to do
|
91
|
+
# so for certain characters in the string, those chars are probably incomplete
|
92
|
+
# multibyte characters.
|
93
|
+
string.dup.force_encoding(Encoding::UTF_8).match?(NULL_BYTE_REGEX)
|
94
|
+
rescue ArgumentError, Encoding::UndefinedConversionError
|
95
|
+
# If we're here, we caught a malformed string. Return true
|
96
|
+
true
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|