lambda-microvms 0.0.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 +7 -0
- data/CHANGELOG.md +14 -0
- data/LICENSE.txt +21 -0
- data/README.md +179 -0
- data/exe/lambda-microvms +102 -0
- data/lib/lambda/microvms/client.rb +93 -0
- data/lib/lambda/microvms/deployer.rb +58 -0
- data/lib/lambda/microvms/doctor.rb +62 -0
- data/lib/lambda/microvms/endpoint.rb +73 -0
- data/lib/lambda/microvms/error.rb +31 -0
- data/lib/lambda/microvms/image.rb +48 -0
- data/lib/lambda/microvms/microvm.rb +111 -0
- data/lib/lambda/microvms/packager.rb +35 -0
- data/lib/lambda/microvms/project.rb +104 -0
- data/lib/lambda/microvms/scaffold.rb +162 -0
- data/lib/lambda/microvms/session.rb +31 -0
- data/lib/lambda/microvms/util.rb +34 -0
- data/lib/lambda/microvms/version.rb +7 -0
- data/lib/lambda/microvms/waiter.rb +33 -0
- data/lib/lambda/microvms.rb +27 -0
- data/sig/lambda/microvms.rbs +153 -0
- metadata +113 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: de35523913c4cd5c11fc48549081c7901db9fdacfef2a72618264885596bb838
|
|
4
|
+
data.tar.gz: 960ec7995dc496da3f358c6ad484eb0a420140afa727275b92e5eadf7201ce2b
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 055d951f5d0a5dc948195eff437d7b67070faf74e5dba4eee65b583aa70fda29a0452d93ecbe4e311bf3e7f5f18054d999776c6c5294bc2c88b061c8a777c77c
|
|
7
|
+
data.tar.gz: 6948f65e7391fa14734dcd1f89757bfb6a1b2a04ee200566409de0851da3a80f41a7173607cbd6efa40a5bc31d09d1033237512eb145bf23358459871dd77da5
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.1.0 Unreleased
|
|
4
|
+
|
|
5
|
+
- Initial Ruby development kit for AWS Lambda MicroVMs.
|
|
6
|
+
- Client wrapper over `aws-sdk-lambda`.
|
|
7
|
+
- Image, MicroVM, Endpoint, Waiter, and session helper APIs.
|
|
8
|
+
- Project scaffolding via `lambda-microvms new NAME`.
|
|
9
|
+
- Generated Ruby MicroVM projects include `aws_lambda_ric` for guest-side Lambda Runtime API integration.
|
|
10
|
+
- Project configuration via `microvm.yml`.
|
|
11
|
+
- Packaging helper via `lambda-microvms package`.
|
|
12
|
+
- Deployment helper via `lambda-microvms deploy` using S3 artifact upload and MicroVM image creation.
|
|
13
|
+
- Runtime helper via `lambda-microvms run`.
|
|
14
|
+
- Project readiness checks via `lambda-microvms doctor`.
|
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Kenneth C. Demanawa
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
# lambda-microvms
|
|
2
|
+
|
|
3
|
+
[](https://badge.fury.io/rb/lambda-microvms)
|
|
4
|
+
[](https://github.com/kanutocd/lambda-microvms/actions)
|
|
5
|
+
[](https://www.ruby-lang.org/en/)
|
|
6
|
+
[](https://opensource.org/licenses/MIT)
|
|
7
|
+
|
|
8
|
+
A Ruby development kit for AWS Lambda MicroVMs.
|
|
9
|
+
|
|
10
|
+
`lambda-microvms` builds on the official `aws-sdk-lambda` gem. It provides Ruby resource objects, lifecycle helpers, endpoint calls, project scaffolding, packaging, and deployment commands for Lambda MicroVM applications.
|
|
11
|
+
|
|
12
|
+
It gives Ruby developers a higher-level workflow:
|
|
13
|
+
|
|
14
|
+
```text
|
|
15
|
+
scaffold
|
|
16
|
+
↓
|
|
17
|
+
develop
|
|
18
|
+
↓
|
|
19
|
+
package
|
|
20
|
+
↓
|
|
21
|
+
deploy image
|
|
22
|
+
↓
|
|
23
|
+
run / suspend / resume / terminate
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Why `aws_lambda_ric` appears in generated projects
|
|
27
|
+
|
|
28
|
+
Ruby MicroVM applications need a runtime inside the image. For non-AWS or OS-only Ruby container images, AWS documents the Ruby Runtime Interface Client (`aws_lambda_ric`) as the component that lets Ruby code interact with the Lambda Runtime API.
|
|
29
|
+
|
|
30
|
+
`lambda-microvms` uses `aws_lambda_ric` as a **generated project dependency**, not as a host-side SDK dependency.
|
|
31
|
+
|
|
32
|
+
```text
|
|
33
|
+
Host side:
|
|
34
|
+
lambda-microvms
|
|
35
|
+
aws-sdk-lambda
|
|
36
|
+
aws-sdk-s3
|
|
37
|
+
|
|
38
|
+
Guest side / inside generated image:
|
|
39
|
+
aws_lambda_ric
|
|
40
|
+
your Ruby handler
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Installation
|
|
44
|
+
|
|
45
|
+
```ruby
|
|
46
|
+
gem "lambda-microvms"
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Scaffold a Ruby MicroVM project
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
lambda-microvms new hello-worker
|
|
53
|
+
cd hello-worker
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Generated project:
|
|
57
|
+
|
|
58
|
+
```text
|
|
59
|
+
hello-worker/
|
|
60
|
+
Dockerfile
|
|
61
|
+
Gemfile
|
|
62
|
+
app.rb
|
|
63
|
+
microvm.yml
|
|
64
|
+
README.md
|
|
65
|
+
.env.example
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
The generated `Gemfile` includes:
|
|
69
|
+
|
|
70
|
+
```ruby
|
|
71
|
+
gem "aws_lambda_ric"
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Check readiness
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
lambda-microvms doctor
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Checks Ruby, Docker, AWS CLI, project config, Dockerfile, deployment bucket, runtime role, and `aws_lambda_ric` presence.
|
|
81
|
+
|
|
82
|
+
## Package
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
lambda-microvms package
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Creates the source artifact configured by `microvm.yml`.
|
|
89
|
+
|
|
90
|
+
## Deploy
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
lambda-microvms deploy
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Deployment flow:
|
|
97
|
+
|
|
98
|
+
```text
|
|
99
|
+
zip project
|
|
100
|
+
↓
|
|
101
|
+
upload to S3
|
|
102
|
+
↓
|
|
103
|
+
create MicroVM image through aws-sdk-lambda
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
The exact AWS Lambda MicroVM API parameter names are still kept close to AWS naming. Put API-specific values under `image.create` in `microvm.yml`.
|
|
107
|
+
|
|
108
|
+
## Run
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
lambda-microvms run
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Runs the configured image ARN with the configured role ARN and runtime payload.
|
|
115
|
+
|
|
116
|
+
## Ruby SDK usage
|
|
117
|
+
|
|
118
|
+
```ruby
|
|
119
|
+
require "lambda/microvms"
|
|
120
|
+
|
|
121
|
+
client = Lambda::MicroVMs::Client.new(region: "us-east-1")
|
|
122
|
+
|
|
123
|
+
image = client.image("arn:aws:lambda:us-east-1:123456789012:microvm-image:ruby-sandbox")
|
|
124
|
+
|
|
125
|
+
vm = image.run(
|
|
126
|
+
role_arn: "arn:aws:iam::123456789012:role/lambda-microvm-runtime",
|
|
127
|
+
payload: { tenant_id: "tenant_123" }
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
vm.wait_until_running
|
|
131
|
+
|
|
132
|
+
response = vm.post("/process", json: { hello: "world" })
|
|
133
|
+
|
|
134
|
+
vm.suspend
|
|
135
|
+
vm.resume
|
|
136
|
+
vm.terminate
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
## Session helper
|
|
140
|
+
|
|
141
|
+
```ruby
|
|
142
|
+
Lambda::MicroVMs.session(
|
|
143
|
+
image_arn: "arn:aws:lambda:us-east-1:123456789012:microvm-image:ruby-sandbox",
|
|
144
|
+
role_arn: "arn:aws:iam::123456789012:role/lambda-microvm-runtime",
|
|
145
|
+
after: :suspend,
|
|
146
|
+
payload: { tenant_id: "tenant_123" }
|
|
147
|
+
) do |vm|
|
|
148
|
+
vm.post("/process", json: { event_id: "evt_1" })
|
|
149
|
+
end
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
Supported cleanup policies:
|
|
153
|
+
|
|
154
|
+
```ruby
|
|
155
|
+
after: :keep
|
|
156
|
+
after: :suspend
|
|
157
|
+
after: :terminate
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
## Architecture
|
|
161
|
+
|
|
162
|
+
```text
|
|
163
|
+
aws-sdk-lambda
|
|
164
|
+
│
|
|
165
|
+
▼
|
|
166
|
+
Lambda::MicroVMs::Client
|
|
167
|
+
│
|
|
168
|
+
├── Image
|
|
169
|
+
├── MicroVM
|
|
170
|
+
├── Endpoint
|
|
171
|
+
├── Session
|
|
172
|
+
├── Scaffold
|
|
173
|
+
├── Packager
|
|
174
|
+
└── Deployer
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
## Status
|
|
178
|
+
|
|
179
|
+
This is an early implementation. Lambda MicroVMs is new, so generated AWS SDK operation shapes may evolve. Unsupported low-level operations raise `Lambda::MicroVMs::UnsupportedOperationError` with an upgrade hint.
|
data/exe/lambda-microvms
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require 'lambda/microvms'
|
|
5
|
+
|
|
6
|
+
module Lambda
|
|
7
|
+
module MicroVMs
|
|
8
|
+
# Minimal CLI for the Ruby Lambda MicroVM development kit.
|
|
9
|
+
class CLI
|
|
10
|
+
def initialize(argv, out: $stdout, err: $stderr)
|
|
11
|
+
@argv = argv
|
|
12
|
+
@out = out
|
|
13
|
+
@err = err
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# rubocop:disable Metrics/MethodLength
|
|
17
|
+
def run
|
|
18
|
+
command = @argv.shift
|
|
19
|
+
case command
|
|
20
|
+
when 'version', '--version', '-v' then version
|
|
21
|
+
when 'new' then new_project
|
|
22
|
+
when 'package' then package
|
|
23
|
+
when 'deploy' then deploy
|
|
24
|
+
when 'run' then run_microvm
|
|
25
|
+
when 'doctor' then doctor
|
|
26
|
+
when 'help', nil then help
|
|
27
|
+
else
|
|
28
|
+
@err.puts "unknown command: #{command}"
|
|
29
|
+
help
|
|
30
|
+
1
|
|
31
|
+
end
|
|
32
|
+
rescue Error, ArgumentError, LoadError => e
|
|
33
|
+
@err.puts "error: #{e.message}"
|
|
34
|
+
1
|
|
35
|
+
end
|
|
36
|
+
# rubocop:enable Metrics/MethodLength
|
|
37
|
+
|
|
38
|
+
private
|
|
39
|
+
|
|
40
|
+
def version
|
|
41
|
+
@out.puts VERSION
|
|
42
|
+
0
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def new_project
|
|
46
|
+
name = @argv.shift || raise(ArgumentError, 'usage: lambda-microvms new NAME')
|
|
47
|
+
force = @argv.include?('--force')
|
|
48
|
+
Scaffold.new(name, force: force).create
|
|
49
|
+
@out.puts "created #{name}"
|
|
50
|
+
0
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def project
|
|
54
|
+
@project ||= Project.load
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def package
|
|
58
|
+
path = Packager.new(project).package
|
|
59
|
+
@out.puts path
|
|
60
|
+
0
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def deploy
|
|
64
|
+
image = Deployer.new(project: project).deploy
|
|
65
|
+
@out.puts image.arn || image.data.inspect
|
|
66
|
+
0
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def run_microvm
|
|
70
|
+
vm = Deployer.new(project: project).run
|
|
71
|
+
@out.puts vm.id || vm.data.inspect
|
|
72
|
+
0
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def doctor
|
|
76
|
+
checks = Doctor.new(project: project).checks
|
|
77
|
+
checks.each do |check|
|
|
78
|
+
marker = check.ok ? 'PASS' : 'FAIL'
|
|
79
|
+
@out.puts "#{marker} #{check.name}: #{check.detail}"
|
|
80
|
+
end
|
|
81
|
+
checks.all?(&:ok) ? 0 : 1
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def help
|
|
85
|
+
@out.puts <<~HELP
|
|
86
|
+
lambda-microvms #{VERSION}
|
|
87
|
+
|
|
88
|
+
Commands:
|
|
89
|
+
new NAME Generate a Ruby Lambda MicroVM project using aws_lambda_ric
|
|
90
|
+
doctor Check local project readiness
|
|
91
|
+
package Create deployable source artifact
|
|
92
|
+
deploy Package, upload to S3, and create a MicroVM image
|
|
93
|
+
run Run the configured MicroVM image
|
|
94
|
+
version Print version
|
|
95
|
+
HELP
|
|
96
|
+
0
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
exit Lambda::MicroVMs::CLI.new(ARGV).run
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
begin
|
|
4
|
+
require 'aws-sdk-lambda'
|
|
5
|
+
rescue LoadError
|
|
6
|
+
nil
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
module Lambda
|
|
10
|
+
module MicroVMs
|
|
11
|
+
# Ruby wrapper over Aws::Lambda::Client for Lambda MicroVM lifecycle operations.
|
|
12
|
+
class Client
|
|
13
|
+
attr_reader :sdk
|
|
14
|
+
|
|
15
|
+
def initialize(region: nil, profile: nil, sdk: nil, **)
|
|
16
|
+
@sdk = sdk || build_sdk(region:, profile:, **)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def image(arn)
|
|
20
|
+
Image.new(client: self, arn: arn)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def microvm(id_or_arn)
|
|
24
|
+
MicroVM.new(client: self, id: id_or_arn)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def create_image(**params)
|
|
28
|
+
response = call_sdk(:create_microvm_image, **params)
|
|
29
|
+
Image.from_response(client: self, response: response)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def get_image(**params)
|
|
33
|
+
response = call_sdk(:get_microvm_image, **params)
|
|
34
|
+
Image.from_response(client: self, response: response)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def delete_image(**params)
|
|
38
|
+
call_sdk(:delete_microvm_image, **params)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def run(**params)
|
|
42
|
+
response = call_sdk(:run_microvm, **params)
|
|
43
|
+
MicroVM.from_response(client: self, response: response)
|
|
44
|
+
end
|
|
45
|
+
alias run_microvm run
|
|
46
|
+
|
|
47
|
+
def get_microvm(**params)
|
|
48
|
+
response = call_sdk(:get_microvm, **params)
|
|
49
|
+
MicroVM.from_response(client: self, response: response)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def list_microvms(**params)
|
|
53
|
+
call_sdk(:list_microvms, **params)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def suspend_microvm(**params)
|
|
57
|
+
call_sdk(:suspend_microvm, **params)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def resume_microvm(**params)
|
|
61
|
+
call_sdk(:resume_microvm, **params)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def terminate_microvm(**params)
|
|
65
|
+
call_sdk(:terminate_microvm, **params)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def create_auth_token(**params)
|
|
69
|
+
call_sdk(:create_microvm_auth_token, **params)
|
|
70
|
+
end
|
|
71
|
+
alias create_microvm_auth_token create_auth_token
|
|
72
|
+
|
|
73
|
+
def call_sdk(operation, **params)
|
|
74
|
+
unless @sdk.respond_to?(operation)
|
|
75
|
+
raise UnsupportedOperationError, "Aws::Lambda::Client does not expose ##{operation}; upgrade aws-sdk-lambda"
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
@sdk.public_send(operation, **params)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
private
|
|
82
|
+
|
|
83
|
+
def build_sdk(region:, profile:, **options)
|
|
84
|
+
raise LoadError, 'install aws-sdk-lambda to use Lambda::MicroVMs::Client' unless defined?(Aws::Lambda::Client)
|
|
85
|
+
|
|
86
|
+
args = options.dup
|
|
87
|
+
args[:region] = region if region
|
|
88
|
+
args[:profile] = profile if profile
|
|
89
|
+
Aws::Lambda::Client.new(**args)
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'securerandom'
|
|
4
|
+
|
|
5
|
+
begin
|
|
6
|
+
require 'aws-sdk-s3'
|
|
7
|
+
rescue LoadError
|
|
8
|
+
nil
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
module Lambda
|
|
12
|
+
module MicroVMs
|
|
13
|
+
# Project-aware deployment helper: package, upload to S3, create MicroVM image.
|
|
14
|
+
class Deployer
|
|
15
|
+
attr_reader :project, :client, :s3
|
|
16
|
+
|
|
17
|
+
def initialize(project:, client: nil, s3: nil) # rubocop:disable Naming/MethodParameterName
|
|
18
|
+
@project = project
|
|
19
|
+
@client = client || Client.new(region: project.region, profile: project.profile)
|
|
20
|
+
@s3 = s3 || build_s3
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def package
|
|
24
|
+
Packager.new(project).package
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def upload(path)
|
|
28
|
+
bucket = project.require!('deployment.bucket', project.s3_bucket)
|
|
29
|
+
key = [project.s3_prefix.sub(%r{/\z}, ''), File.basename(path)].join('/')
|
|
30
|
+
s3.put_object(bucket: bucket, key: key, body: File.open(path, 'rb'))
|
|
31
|
+
"s3://#{bucket}/#{key}"
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def deploy
|
|
35
|
+
artifact = package
|
|
36
|
+
artifact_uri = upload(artifact)
|
|
37
|
+
client.create_image(**project.create_image_params(artifact_uri: artifact_uri))
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def run
|
|
41
|
+
image_arn = project.require!('image.arn', project.image_arn)
|
|
42
|
+
role_arn = project.require!('role_arn', project.role_arn)
|
|
43
|
+
client.image(image_arn).run(**project.run_params, role_arn: role_arn)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
private
|
|
47
|
+
|
|
48
|
+
def build_s3
|
|
49
|
+
raise LoadError, 'install aws-sdk-s3 to use deployment helpers' unless defined?(Aws::S3::Client)
|
|
50
|
+
|
|
51
|
+
args = {}
|
|
52
|
+
args[:region] = project.region if project.region
|
|
53
|
+
args[:profile] = project.profile if project.profile
|
|
54
|
+
Aws::S3::Client.new(**args)
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'English'
|
|
4
|
+
module Lambda
|
|
5
|
+
module MicroVMs
|
|
6
|
+
# Performs lightweight local project readiness checks.
|
|
7
|
+
class Doctor
|
|
8
|
+
Check = Struct.new(:name, :ok, :detail, keyword_init: true)
|
|
9
|
+
|
|
10
|
+
attr_reader :project, :runner
|
|
11
|
+
|
|
12
|
+
def initialize(project:, runner: Kernel)
|
|
13
|
+
@project = project
|
|
14
|
+
@runner = runner
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def checks
|
|
18
|
+
[
|
|
19
|
+
check('Ruby', RUBY_VERSION >= '3.2', RUBY_VERSION),
|
|
20
|
+
command_check('Docker', 'docker --version'),
|
|
21
|
+
command_check('AWS CLI', 'aws --version'),
|
|
22
|
+
file_check('microvm.yml', project.config_path),
|
|
23
|
+
file_check('Dockerfile', project.dockerfile),
|
|
24
|
+
config_check('role_arn', project.role_arn),
|
|
25
|
+
config_check('deployment.bucket', project.s3_bucket),
|
|
26
|
+
ric_check
|
|
27
|
+
]
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def ok?
|
|
31
|
+
checks.all?(&:ok)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
private
|
|
35
|
+
|
|
36
|
+
def check(name, ok, detail) # rubocop:disable Naming/MethodParameterName
|
|
37
|
+
Check.new(name:, ok:, detail: detail.to_s)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def command_check(name, command)
|
|
41
|
+
output = `#{command} 2>&1`.strip
|
|
42
|
+
Check.new(name:, ok: $CHILD_STATUS.success?, detail: output)
|
|
43
|
+
rescue StandardError => e
|
|
44
|
+
Check.new(name:, ok: false, detail: e.message)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def file_check(name, path)
|
|
48
|
+
Check.new(name:, ok: File.exist?(path), detail: path)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def config_check(name, value)
|
|
52
|
+
Check.new(name:, ok: value && value != '', detail: value || 'missing')
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def ric_check
|
|
56
|
+
gemfile = File.join(project.root, 'Gemfile')
|
|
57
|
+
ok = File.exist?(gemfile) && File.read(gemfile).include?('aws_lambda_ric')
|
|
58
|
+
Check.new(name: 'aws_lambda_ric', ok: ok, detail: ok ? 'present in Gemfile' : 'missing from Gemfile')
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'json'
|
|
4
|
+
require 'net/http'
|
|
5
|
+
require 'uri'
|
|
6
|
+
|
|
7
|
+
module Lambda
|
|
8
|
+
module MicroVMs
|
|
9
|
+
# HTTP client for a single Lambda MicroVM endpoint.
|
|
10
|
+
class Endpoint
|
|
11
|
+
attr_reader :url
|
|
12
|
+
|
|
13
|
+
def initialize(url:, token:, http: Net::HTTP)
|
|
14
|
+
@url = url
|
|
15
|
+
@token = token
|
|
16
|
+
@http = http
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def get(path, headers: {})
|
|
20
|
+
request(Net::HTTP::Get, path, headers:)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def post(path, json: nil, body: nil, headers: {})
|
|
24
|
+
request(Net::HTTP::Post, path, json:, body:, headers:)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def request(klass, path, json: nil, body: nil, headers: {})
|
|
28
|
+
response = @http.start(uri.host, uri.port, use_ssl: uri.scheme == 'https') do |http|
|
|
29
|
+
http.request(build_request(klass, path, headers:, body:, json:))
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
success = response.code.to_i.between?(200, 299)
|
|
33
|
+
unless success
|
|
34
|
+
raise EndpointError.new("MicroVM endpoint returned HTTP #{response.code}", status: response.code.to_i,
|
|
35
|
+
body: response.body)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
parse_response(response)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
private
|
|
42
|
+
|
|
43
|
+
def build_uri(path)
|
|
44
|
+
base = URI(@url)
|
|
45
|
+
return base if path.nil? || path.empty? || path == '/'
|
|
46
|
+
|
|
47
|
+
joined = [base.path.sub(%r{/\z}, ''), path.sub(%r{\A/}, '')].reject(&:empty?).join('/')
|
|
48
|
+
base.path = "/#{joined}"
|
|
49
|
+
base
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def parse_response(response)
|
|
53
|
+
content_type = response['Content-Type'].to_s
|
|
54
|
+
return JSON.parse(response.body) if content_type.include?('application/json') && !response.body.to_s.empty?
|
|
55
|
+
|
|
56
|
+
response.body
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def build_request(klass, path, headers: {}, json: nil, body: nil)
|
|
60
|
+
uri = build_uri(path)
|
|
61
|
+
request = klass.new(uri)
|
|
62
|
+
headers = headers.merge('Authorization' => @token ? "Bearer #{@token}" : nil)
|
|
63
|
+
if json
|
|
64
|
+
headers['Content-Type'] ||= 'application/json'
|
|
65
|
+
request.body = JSON.generate(json)
|
|
66
|
+
elsif body
|
|
67
|
+
request.body = body
|
|
68
|
+
end
|
|
69
|
+
headers.each_pair.with_object(request) { |(key, value), req| req[key] = value }
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Lambda
|
|
4
|
+
module MicroVMs
|
|
5
|
+
# Base error for lambda-microvms.
|
|
6
|
+
class Error < StandardError; end
|
|
7
|
+
|
|
8
|
+
# Raised when an expected MicroVM lifecycle state is not reached in time.
|
|
9
|
+
class WaitTimeoutError < Error; end
|
|
10
|
+
|
|
11
|
+
# Raised when the configured AWS Lambda SDK client does not expose a needed operation.
|
|
12
|
+
class UnsupportedOperationError < Error; end
|
|
13
|
+
|
|
14
|
+
# Raised when project configuration is invalid or incomplete.
|
|
15
|
+
class ConfigurationError < Error; end
|
|
16
|
+
|
|
17
|
+
# Raised when an external command such as Docker fails.
|
|
18
|
+
class CommandError < Error; end
|
|
19
|
+
|
|
20
|
+
# Raised when a MicroVM endpoint call fails with a non-success response.
|
|
21
|
+
class EndpointError < Error
|
|
22
|
+
attr_reader :status, :body
|
|
23
|
+
|
|
24
|
+
def initialize(message, status:, body:)
|
|
25
|
+
@status = status
|
|
26
|
+
@body = body
|
|
27
|
+
super(message)
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Lambda
|
|
4
|
+
module MicroVMs
|
|
5
|
+
# Represents a Lambda MicroVM image/snapshot artifact.
|
|
6
|
+
class Image
|
|
7
|
+
attr_reader :client, :arn, :data
|
|
8
|
+
|
|
9
|
+
def self.from_response(client:, response:)
|
|
10
|
+
arn = Util.extract(response, :image_arn, :microvm_image_arn, :arn)
|
|
11
|
+
new(client: client, arn: arn, data: response)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def initialize(client:, arn:, data: nil)
|
|
15
|
+
@client = client
|
|
16
|
+
@arn = arn
|
|
17
|
+
@data = data
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def refresh
|
|
21
|
+
fresh = client.get_image(image_arn: arn)
|
|
22
|
+
@data = fresh.data
|
|
23
|
+
self
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def state
|
|
27
|
+
Util.normalize_state(Util.extract(@data, :state, :status, :image_state))
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def ready?
|
|
31
|
+
%i[ready available active].include?(state)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def wait_until_ready(delay: Waiter::DEFAULT_DELAY, timeout: Waiter::DEFAULT_TIMEOUT)
|
|
35
|
+
Waiter.new(delay: delay, timeout: timeout).wait(message: "image #{arn} to be ready") do
|
|
36
|
+
refresh.ready?
|
|
37
|
+
end
|
|
38
|
+
self
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def run(role_arn:, payload: nil, **params)
|
|
42
|
+
request = params.merge(image_arn: arn, role_arn: role_arn)
|
|
43
|
+
request[:payload] = payload if payload
|
|
44
|
+
client.run(**request)
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|