functions_framework 0.0.0 → 0.1.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d98d20b3b8e7ab30b0ef44dfee51e6df6ab8f1f2816ee72e50ecaaca099e0fe5
4
- data.tar.gz: 1ee993fdaea21fff0c2d6f551d4cea3d67f0ce0264ee0d4fdf6f1c656c7e8211
3
+ metadata.gz: d09328e219e183d857a5b0e5cbb4a9aac77dcb2b6deea96ecb4909729aac5e46
4
+ data.tar.gz: 2ffdd21b22c08193b64610ac42ee05f6b5627c25b99ee6d2d45eb52408438e33
5
5
  SHA512:
6
- metadata.gz: 698ee184e253b7cf071bcf3d1065ed18f06c93e58fae662fbc72ddffa5ea8c92a051d7045fc2afd383f114ac5a46d8d72759eac93b283bd43d4d263c163bd050
7
- data.tar.gz: 6bb25e647fc7275311a3dab8cb455214dc58c074d45c5ac17bbf967381d4903e7defeedf74840a5cae045b7d6dc0bd31cbf8e539a8294d43764ec264ec9b94a9
6
+ metadata.gz: 93cf161a26eee8349e99ec7012698374206e333099c94ecff07609bdefe9f7a33bf331aa3a0b305fd005e0a89ba9988bcc3ed3ce4c2c1edadd587975561b5fe8
7
+ data.tar.gz: 64eea2572a244dca261c090b6e51445b2f59ad2ad985edb29dc2a52aaa902fd9d9afa44e65e6ca51a6653c16d5532ace49cb9a6defd09c45195d9d81e9238707
data/.yardopts CHANGED
@@ -7,5 +7,4 @@
7
7
  ./lib/functions_framework.rb
8
8
  -
9
9
  README.md
10
- CONTRIBUTING.md
11
10
  CHANGELOG.md
data/CHANGELOG.md CHANGED
@@ -1,2 +1,5 @@
1
1
  # Changelog
2
2
 
3
+ ### v0.1.0 / 2020-01-30
4
+
5
+ * Initial release
data/README.md CHANGED
@@ -1,12 +1,61 @@
1
1
  # Functions Framework
2
2
 
3
+ An open source framework for writing lightweight, portable Ruby functions that
4
+ run in a serverless environment. Functions written to this Framework will run
5
+ in many different environments, including:
6
+
7
+ * [Google Cloud Functions](https://cloud.google.com/functions) *(coming soon)*
8
+ * [Cloud Run or Cloud Run for Anthos](https://cloud.google.com/run)
9
+ * Any other [Knative](https://github.com/knative)-based environment
10
+ * Your local development machine
11
+
12
+ The framework allows you to go from:
13
+
14
+ ```ruby
15
+ FunctionsFramework.http do |request|
16
+ "Hello, world!\n"
17
+ end
18
+ ```
19
+
20
+ To:
21
+
22
+ ```sh
23
+ curl http://my-url
24
+ # Output: Hello, world!
25
+ ```
26
+
27
+ All without needing to worry about writing an HTTP server or complicated
28
+ request handling logic.
29
+
30
+ For more information about the Functions Framework, see
31
+ https://github.com/GoogleCloudPlatform/functions-framework
32
+
33
+ ## Features
34
+
35
+ * Define named functions using normal Ruby constructs.
36
+ * Invoke functions in response to requests.
37
+ * Automatically unmarshal events conforming to the
38
+ [CloudEvents](https://cloudevents.io) spec.
39
+ * Spin up a local development server for quick testing.
40
+ * Integrate with standard Ruby libraries such as Rack and Minitest.
41
+ * Portable between serverless platforms.
42
+
3
43
  ## Installation
4
44
 
45
+ Install the Functions Framework via Rubygems:
46
+
47
+ ```sh
48
+ gem install functions_framework
5
49
  ```
6
- $ gem install functions_framework
50
+
51
+ Or add it to your Gemfile for installation using Bundler:
52
+
53
+ ```ruby
54
+ # Gemfile
55
+ gem "functions_framework", "~> 0.1"
7
56
  ```
8
57
 
9
- ## Supported Ruby Versions
58
+ ### Supported Ruby versions
10
59
 
11
60
  This library is supported on Ruby 2.4+.
12
61
 
@@ -16,3 +65,154 @@ in security maintenance, and not end of life. Currently, this means Ruby 2.4
16
65
  and later. Older versions of Ruby _may_ still work, but are unsupported and not
17
66
  recommended. See https://www.ruby-lang.org/en/downloads/branches/ for details
18
67
  about the Ruby support schedule.
68
+
69
+ ## Quickstart
70
+
71
+ ### Running Hello World on your local machine
72
+
73
+ Create a `Gemfile` listing the Functions Framework as a dependency:
74
+
75
+ ```ruby
76
+ # Gemfile
77
+ source "https://rubygems.org"
78
+ gem "functions_framework", "~> 0.1"
79
+ ```
80
+
81
+ Create a file called `app.rb` and include the following code. This defines a
82
+ simple function called "hello".
83
+
84
+ ```ruby
85
+ # app.rb
86
+ require "functions_framework"
87
+
88
+ FunctionsFramework.http("hello") do |request|
89
+ "Hello, world!\n"
90
+ end
91
+ ```
92
+
93
+ Install the bundle, and start the framework. This spins up a local web server
94
+ running your "hello" function:
95
+
96
+ ```sh
97
+ bundle install
98
+ # ...installs the functions_framework gem and other dependencies
99
+ bundle exec functions-framework --target hello
100
+ # ...starts the web server in the foreground
101
+ ```
102
+
103
+ In a separate shell, you can send requests to this function using curl:
104
+
105
+ ```sh
106
+ curl localhost:8080
107
+ # Output: Hello, world!
108
+ ```
109
+
110
+ Stop the web server with `CTRL+C`.
111
+
112
+ ### Deploying a function to Google Cloud Functions
113
+
114
+ (Google Cloud Functions does not yet support the Ruby Function Framework)
115
+
116
+ ### Deploying a function to Cloud Run
117
+
118
+ Create a `Dockerfile` for your function:
119
+
120
+ ```dockerfile
121
+ # Dockerfile
122
+ FROM ruby:2.6
123
+
124
+ WORKDIR /app
125
+ COPY . .
126
+ RUN gem install --no-document bundler \
127
+ && bundle config --local frozen true \
128
+ && bundle install
129
+
130
+ ENTRYPOINT ["bundle", "exec", "functions-framework"]
131
+ CMD ["--target", "hello"]
132
+ ```
133
+
134
+ Build your function into a Docker image:
135
+
136
+ ```sh
137
+ gcloud builds submit --tag=gcr.io/[YOUR-PROJECT]/hello:build-1
138
+ ```
139
+
140
+ Deploy to Cloud Run:
141
+
142
+ ```sh
143
+ gcloud run deploy hello --image=gcr.io/[YOUR-PROJECT]/hello:build-1 \
144
+ --platform=managed --allow-unauthenticated --region=us-central1
145
+ ```
146
+
147
+ You can use a similar approach to deploy to any other Knative-based serverless
148
+ environment.
149
+
150
+ ### Responding to CloudEvents
151
+
152
+ You can also define a function that response to
153
+ [CloudEvents](https://cloudevents.io). The Functions Framework will handle
154
+ unmarshalling of the event data.
155
+
156
+ Change `app.rb` to read:
157
+
158
+ ```ruby
159
+ # app.rb
160
+ require "functions_framework"
161
+
162
+ FunctionsFramework.event("my-handler") do |data, context|
163
+ FunctionsFramework.logger.info "I received #{data.inspect}"
164
+ end
165
+ ```
166
+
167
+ Start up the framework with this new function:
168
+
169
+ ```sh
170
+ bundle install
171
+ bundle exec functions-framework --target my-handler
172
+ ```
173
+
174
+ In a separate shell, you can send a CloudEvent to this function using curl:
175
+
176
+ ```sh
177
+ curl --header "Content-Type: text/plain; charset=utf-8" \
178
+ --header "CE-ID: 12345" \
179
+ --header "CE-Source: curl" \
180
+ --header "CE-Type: com.example.test" \
181
+ --header "CE-Specversion: 1.0" \
182
+ --data "Hello, world!" \
183
+ http://localhost:8080
184
+ ```
185
+
186
+ CloudEvents functions do not return meaningful results, but you will see the
187
+ log message from the web server.
188
+
189
+ ### Configuring the Functions Framework
190
+
191
+ The Ruby Functions Framework recognizes the standard command line arguments to
192
+ the `functions-framework` executable. Each argument also corresponds to an
193
+ environment variable. If you specify both, the environment variable will be
194
+ ignored.
195
+
196
+ Command-line flag | Environment variable | Description
197
+ ----------------- | -------------------- | -----------
198
+ `--port` | `PORT` | The port on which the Functions Framework listens for requests. Default: `8080`
199
+ `--target` | `FUNCTION_TARGET` | The name of the exported function to be invoked in response to requests. Default: `function`
200
+ `--source` | `FUNCTION_SOURCE` | The path to the file containing your function. Default: `app.rb` (in the current working directory)
201
+
202
+ Note: the flag `--signature-type` and corresponding environment variable
203
+ `FUNCTION_SIGNATURE_TYPE` are not used by the Ruby Function Framework, because
204
+ you specify the signature type when defining the function in the source.
205
+
206
+ The Ruby `functions-framework` executable also recognizes several additional
207
+ flags that can be used to control logging verbosity, binding, and other
208
+ parameters. For details, see the online help:
209
+
210
+ ```sh
211
+ functions-framework --help
212
+ ```
213
+
214
+ ## For more information
215
+
216
+ * See the `examples` directory for additional examples
217
+ * Consult https://rubydoc.info/gems/functions_framework for full reference
218
+ documentation.
@@ -0,0 +1,19 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # Copyright 2020 Google LLC
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # https://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+
17
+ require "functions_framework/cli"
18
+
19
+ ::FunctionsFramework::CLI.new.parse_args(::ARGV).run
@@ -0,0 +1,113 @@
1
+ # Copyright 2020 Google LLC
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # https://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ require "optparse"
16
+
17
+ require "functions_framework"
18
+
19
+ module FunctionsFramework
20
+ ##
21
+ # Implementation of the functions-framework executable.
22
+ #
23
+ class CLI
24
+ ##
25
+ # Create a new CLI, setting arguments to their defaults.
26
+ #
27
+ def initialize
28
+ @target = ::ENV["FUNCTION_TARGET"] || ::FunctionsFramework::DEFAULT_TARGET
29
+ @source = ::ENV["FUNCTION_SOURCE"] || ::FunctionsFramework::DEFAULT_SOURCE
30
+ @env = nil
31
+ @port = nil
32
+ @bind = nil
33
+ @min_threads = nil
34
+ @max_threads = nil
35
+ @detailed_errors = nil
36
+ end
37
+
38
+ ##
39
+ # Parse the given command line arguments.
40
+ # Exits if argument parsing failed.
41
+ #
42
+ # @param argv [Array<String>]
43
+ # @return [self]
44
+ #
45
+ def parse_args argv # rubocop:disable Metrics/MethodLength
46
+ option_parser = ::OptionParser.new do |op| # rubocop:disable Metrics/BlockLength
47
+ op.on "-t", "--target TARGET",
48
+ "Set the name of the function to execute (defaults to #{DEFAULT_TARGET})" do |val|
49
+ @target = val
50
+ end
51
+ op.on "-s", "--source SOURCE",
52
+ "Set the source file to load (defaults to #{DEFAULT_SOURCE})" do |val|
53
+ @source = val
54
+ end
55
+ op.on "-p", "--port PORT", "Set the port to listen to (defaults to 8080)" do |val|
56
+ @port = val.to_i
57
+ end
58
+ op.on "-b", "--bind BIND", "Set the address to bind to (defaults to 0.0.0.0)" do |val|
59
+ @bind = val
60
+ end
61
+ op.on "-e", "--environment ENV", "Set the Rack environment" do |val|
62
+ @env = val
63
+ end
64
+ op.on "--min-threads NUM", "Set the minimum threead pool size" do |val|
65
+ @min_threads = val
66
+ end
67
+ op.on "--max-threads NUM", "Set the maximum threead pool size" do |val|
68
+ @max_threads = val
69
+ end
70
+ op.on "--[no-]detailed-errors", "Set whether to show error details" do |val|
71
+ @detailed_errors = val
72
+ end
73
+ op.on "-v", "--verbose", "Increase log verbosity" do
74
+ ::FunctionsFramework.logger.level -= 1
75
+ end
76
+ op.on "-q", "--quiet", "Decrease log verbosity" do
77
+ ::FunctionsFramework.logger.level += 1
78
+ end
79
+ op.on "--help", "Display help" do
80
+ puts op
81
+ exit
82
+ end
83
+ end
84
+ option_parser.parse! argv
85
+ unless argv.empty?
86
+ warn "Unrecognized arguments: #{argv}"
87
+ puts op
88
+ exit 1
89
+ end
90
+ self
91
+ end
92
+
93
+ ##
94
+ # Run the configured server, and block until it stops.
95
+ # @return [self]
96
+ #
97
+ def run
98
+ FunctionsFramework.logger.info \
99
+ "FunctionsFramework: Loading functions from #{@source.inspect}..."
100
+ load @source
101
+ server = ::FunctionsFramework.start @target do |config|
102
+ config.rack_env = @env
103
+ config.port = @port
104
+ config.bind_addr = @bind
105
+ config.show_error_details = @detailed_errors
106
+ config.min_threads = @min_threads
107
+ config.max_threads = @max_threads
108
+ end
109
+ server.wait_until_stopped
110
+ self
111
+ end
112
+ end
113
+ end
@@ -0,0 +1,59 @@
1
+ # Copyright 2020 Google LLC
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # https://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ module FunctionsFramework
16
+ module CloudEvents
17
+ ##
18
+ # A content handler for the binary mode.
19
+ # See https://github.com/cloudevents/spec/blob/master/http-protocol-binding.md
20
+ #
21
+ module BinaryContent
22
+ class << self
23
+ ##
24
+ # Decode an event from the given Rack environment
25
+ #
26
+ # @param env [Hash] Rack environment hash
27
+ # @param content_type [FunctionsFramework::CloudEvents::ContentType]
28
+ # the content type from the Rack environment
29
+ # @return [FunctionsFramework::CloudEvents::Event]
30
+ #
31
+ def decode_rack_env env, content_type
32
+ data = env["rack.input"]&.read
33
+ spec_version = interpret_header env, "HTTP_CE_SPECVERSION"
34
+ raise "Unrecognized specversion: #{spec_version}" unless spec_version == "1.0"
35
+ Event.new \
36
+ id: interpret_header(env, "HTTP_CE_ID"),
37
+ source: interpret_header(env, "HTTP_CE_SOURCE"),
38
+ type: interpret_header(env, "HTTP_CE_TYPE"),
39
+ spec_version: spec_version,
40
+ data: data,
41
+ data_content_type: content_type,
42
+ data_schema: interpret_header(env, "HTTP_CE_DATASCHEMA"),
43
+ subject: interpret_header(env, "HTTP_CE_SUBJECT"),
44
+ time: interpret_header(env, "HTTP_CE_TIME")
45
+ end
46
+
47
+ private
48
+
49
+ def interpret_header env, key
50
+ escaped_value = env[key]
51
+ return nil if escaped_value.nil?
52
+ escaped_value.gsub(/%([0-9a-fA-F]{2})/) do
53
+ [$1.to_i(16)].pack "C" # rubocop:disable Style/PerlBackrefs
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,139 @@
1
+ # Copyright 2020 Google LLC
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # https://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ module FunctionsFramework
16
+ module CloudEvents
17
+ ##
18
+ # A parsed content-type header.
19
+ #
20
+ # This object represents the information contained in a Content-Type,
21
+ # obtained by parsing the header according to RFC 2045.
22
+ #
23
+ # Case-insensitive fields, such as media_type and subtype, are normalized
24
+ # to lower case.
25
+ #
26
+ class ContentType
27
+ ##
28
+ # Parse the given header value
29
+ #
30
+ # @param string [String] Content-Type header value in RFC 2045 format
31
+ #
32
+ def initialize string
33
+ @string = string
34
+ # TODO: This handles simple cases but is not RFC-822 compliant.
35
+ sections = string.to_s.split ";"
36
+ media_type, subtype = sections.shift.split "/"
37
+ subtype_prefix, subtype_format = subtype.split "+"
38
+ @media_type = media_type.strip.downcase
39
+ @subtype = subtype.strip.downcase
40
+ @subtype_prefix = subtype_prefix.strip.downcase
41
+ @subtype_format = subtype_format&.strip&.downcase
42
+ @params = initialize_params sections
43
+ @canonical_string = "#{@media_type}/#{@subtype}" +
44
+ @params.map { |k, v| "; #{k}=#{v}" }.join
45
+ end
46
+
47
+ ##
48
+ # The original header content string
49
+ # @return [String]
50
+ #
51
+ attr_reader :string
52
+ alias to_s string
53
+
54
+ ##
55
+ # A "canonical" header content string with spacing and capitalization
56
+ # normalized.
57
+ # @return [String]
58
+ #
59
+ attr_reader :canonical_string
60
+
61
+ ##
62
+ # The media type.
63
+ # @return [String]
64
+ #
65
+ attr_reader :media_type
66
+
67
+ ##
68
+ # The entire content subtype (which could include an extension delimited
69
+ # by a plus sign)
70
+ # @return [String]
71
+ #
72
+ attr_reader :subtype
73
+
74
+ ##
75
+ # The portion of the content subtype before any plus sign.
76
+ # @return [String]
77
+ #
78
+ attr_reader :subtype_prefix
79
+
80
+ ##
81
+ # The portion of the content subtype after any plus sign, or nil if there
82
+ # is no plus sign in the subtype.
83
+ # @return [String,nil]
84
+ #
85
+ attr_reader :subtype_format
86
+
87
+ ##
88
+ # An array of parameters, each element as a two-element array of the
89
+ # parameter name and value.
90
+ # @return [Array<Array(String,String)>]
91
+ #
92
+ attr_reader :params
93
+
94
+ ##
95
+ # An array of values for the given parameter name
96
+ # @param key [String]
97
+ # @return [Array<String>]
98
+ #
99
+ def param_values key
100
+ key = key.downcase
101
+ @params.inject([]) { |a, (k, v)| key == k ? a << v : a }
102
+ end
103
+
104
+ ##
105
+ # The first value of the "charset" parameter, or nil if there is no
106
+ # charset.
107
+ # @return [String,nil]
108
+ #
109
+ def charset
110
+ param_values("charset").first
111
+ end
112
+
113
+ ## @private
114
+ def == other
115
+ other.is_a?(ContentType) && canonical_string == other.canonical_string
116
+ end
117
+ alias eql? ==
118
+
119
+ ## @private
120
+ def hash
121
+ canonical_string.hash
122
+ end
123
+
124
+ private
125
+
126
+ def initialize_params sections
127
+ params = sections.map do |s|
128
+ k, v = s.split "="
129
+ [k.strip.downcase, v.strip]
130
+ end
131
+ params.sort! do |(k1, v1), (k2, v2)|
132
+ a = k1 <=> k2
133
+ a.zero? ? v1 <=> v2 : a
134
+ end
135
+ params
136
+ end
137
+ end
138
+ end
139
+ end