onlyoffice_digitalocean_wrapper 0.4.0 → 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/lib/onlyoffice_digitalocean_wrapper/digitalocean_wrapper.rb +23 -5
- data/lib/onlyoffice_digitalocean_wrapper/digitalocean_wrapper/exceptions_retryer.rb +2 -2
- data/lib/onlyoffice_digitalocean_wrapper/digitalocean_wrapper/getters.rb +50 -8
- data/lib/onlyoffice_digitalocean_wrapper/digitalocean_wrapper/logger_wrapper.rb +13 -0
- data/lib/onlyoffice_digitalocean_wrapper/digitalocean_wrapper/power_actions.rb +9 -0
- data/lib/onlyoffice_digitalocean_wrapper/digitalocean_wrapper/token_methods.rb +8 -5
- data/lib/onlyoffice_digitalocean_wrapper/version.rb +3 -1
- metadata +112 -13
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 722a209dd7c530acc9eca5dc95af3482ce07b031f1d60458f0fd4f60d68d97f1
|
4
|
+
data.tar.gz: 2176a7c8518150703d75437360e757e2be7e6ed770affb877fb02dfc362f2124
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: efec409153d5b1d2c94d8ef541ed957f708869b5096c42371224d389adab3d7282d0eccb0e56bb40a30dc6e5a43989eae1c2aeb73830346138f9f20511424e31
|
7
|
+
data.tar.gz: 5680e5bc73b4fd25f402fa18de9b788008710ef00fac98dff796e958e1e3e45c682563eeeb169a5b2871af8898a7247e975c81b5f1bf91fe793cb5448714bf6e
|
@@ -1,21 +1,24 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'droplet_kit'
|
4
|
-
require 'onlyoffice_logger_helper'
|
5
4
|
require_relative 'digitalocean_wrapper/digitalocean_exceptions'
|
6
5
|
require_relative 'digitalocean_wrapper/exceptions_retryer'
|
7
6
|
require_relative 'digitalocean_wrapper/getters'
|
7
|
+
require_relative 'digitalocean_wrapper/logger_wrapper'
|
8
8
|
require_relative 'digitalocean_wrapper/power_actions'
|
9
9
|
require_relative 'digitalocean_wrapper/token_methods'
|
10
10
|
|
11
|
+
# Namespace for this gem
|
11
12
|
module OnlyofficeDigitaloceanWrapper
|
12
13
|
# Class for wrapping DigitalOcean API gem
|
13
14
|
class DigitalOceanWrapper
|
14
15
|
include Getters
|
16
|
+
include LoggerWrapper
|
15
17
|
include ExceptionsRetryer
|
16
18
|
include PowerActions
|
17
19
|
include TokenMethods
|
18
20
|
|
21
|
+
# @return [Array<String>] list of allowed droplet sizes
|
19
22
|
DROPLET_SIZES = %w[512mb 1gb 2gb 4gb 8gb 16gb 32gb 48gb 64gb].freeze
|
20
23
|
attr_accessor :client
|
21
24
|
|
@@ -25,20 +28,32 @@ module OnlyofficeDigitaloceanWrapper
|
|
25
28
|
raise ArgumentError, 'DigitalOceanWrapper: Your Access Token is Incorrect' unless correct_access_token?
|
26
29
|
end
|
27
30
|
|
31
|
+
# Wait until droplet has status
|
32
|
+
# @param droplet_name [String] name of droplet
|
33
|
+
# @param status [String] status to wait
|
34
|
+
# @param params [Hash] additiona params
|
35
|
+
# @return [Symbol] droplet status after wait over
|
28
36
|
def wait_until_droplet_have_status(droplet_name, status = 'active', params = {})
|
29
37
|
timeout = params.fetch(:timeout, 300)
|
30
38
|
counter = 0
|
31
39
|
while get_droplet_status_by_name(droplet_name) != status && counter < timeout
|
32
40
|
counter += 10
|
33
41
|
sleep 10
|
34
|
-
|
35
|
-
|
42
|
+
logger.info("waiting for droplet (#{droplet_name}) to have "\
|
43
|
+
"status: #{status} for #{counter} seconds of #{timeout}")
|
36
44
|
end
|
37
45
|
raise DropletOperationTimeout, "#{droplet_name} was not #{status} for #{timeout}s" if counter >= timeout
|
38
46
|
|
39
47
|
get_droplet_status_by_name(droplet_name)
|
40
48
|
end
|
41
49
|
|
50
|
+
# Restore droplet from image by name
|
51
|
+
# @param image_name [String] name of image
|
52
|
+
# @param droplet_name [String] name for droplet
|
53
|
+
# @param region [String] region to restore
|
54
|
+
# @param size [String] size of droplet
|
55
|
+
# @param tags [String, Array<String>] name of tags to apply
|
56
|
+
# @return [Object] object with droplet data
|
42
57
|
def restore_image_by_name(image_name = 'nct-at-stable',
|
43
58
|
droplet_name = image_name,
|
44
59
|
region = 'nyc3',
|
@@ -56,7 +71,7 @@ module OnlyofficeDigitaloceanWrapper
|
|
56
71
|
monitoring: true,
|
57
72
|
size: size)
|
58
73
|
created = @client.droplets.create(droplet)
|
59
|
-
|
74
|
+
logger.info("restore_image_by_name(#{image_name}, #{droplet_name})")
|
60
75
|
if created.is_a?(String)
|
61
76
|
raise "Problem, while creating '#{droplet_name}' from image '#{image_name}'\n" \
|
62
77
|
"Error: #{created}"
|
@@ -64,10 +79,13 @@ module OnlyofficeDigitaloceanWrapper
|
|
64
79
|
created
|
65
80
|
end
|
66
81
|
|
82
|
+
# Destroy droplet by name
|
83
|
+
# @param droplet_name [String] name of droplet
|
84
|
+
# @return [Symbol] Droplet status after destruction
|
67
85
|
def destroy_droplet_by_name(droplet_name = 'nct-at1')
|
68
86
|
droplet_id = get_droplet_id_by_name(droplet_name)
|
69
87
|
client.droplets.delete(id: droplet_id)
|
70
|
-
|
88
|
+
logger.info("destroy_droplet_by_name(#{droplet_name})")
|
71
89
|
wait_until_droplet_have_status(droplet_name, nil)
|
72
90
|
end
|
73
91
|
end
|
@@ -15,8 +15,8 @@ module OnlyofficeDigitaloceanWrapper
|
|
15
15
|
yield
|
16
16
|
rescue exception => e
|
17
17
|
try += 1
|
18
|
-
|
19
|
-
|
18
|
+
logger.error("Error '#{exception}, #{e}' happened during "\
|
19
|
+
"operation. Retrying #{try} of #{retries}")
|
20
20
|
sleep timeout # Time to cooldown error
|
21
21
|
try <= retries ? retry : raise
|
22
22
|
end
|
@@ -8,7 +8,7 @@ module OnlyofficeDigitaloceanWrapper
|
|
8
8
|
image = all_droplets.find { |x| x.name == image_name }
|
9
9
|
raise DigitalOceanImageNotFound, image_name if image.nil?
|
10
10
|
|
11
|
-
|
11
|
+
logger.info("get_image_id_by_name(#{image_name}): #{image.id}")
|
12
12
|
image.id
|
13
13
|
end
|
14
14
|
|
@@ -22,43 +22,85 @@ module OnlyofficeDigitaloceanWrapper
|
|
22
22
|
end
|
23
23
|
end
|
24
24
|
|
25
|
+
# Get project by name
|
26
|
+
# @param [String] project_name
|
27
|
+
# @return [DropletKit::Project] object representing a project
|
28
|
+
def project_by_name(project_name)
|
29
|
+
retry_exception do
|
30
|
+
projects = @client.projects.all
|
31
|
+
projects.find { |x| x.name == project_name }
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
# Get project id by name
|
36
|
+
# @param [String] project_name
|
37
|
+
# @return [nil, String] id of current project or nil is no project found
|
38
|
+
def get_project_id_by_name(project_name)
|
39
|
+
project = project_by_name(project_name)
|
40
|
+
if project.nil?
|
41
|
+
logger.info("get_project_id_by_name(#{project_name}): not found any projects")
|
42
|
+
nil
|
43
|
+
else
|
44
|
+
logger.info("get_project_id_by_name(#{project_name}): #{project.id}")
|
45
|
+
project.id
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
# Return droplet id by it's name
|
50
|
+
# @param droplet_name [String] name of droplet
|
51
|
+
# @return [nil, Integer] id of droplet or nil is no one droplet found
|
25
52
|
def get_droplet_id_by_name(droplet_name)
|
26
53
|
droplet = droplet_by_name(droplet_name)
|
27
54
|
if droplet.nil?
|
28
|
-
|
55
|
+
logger.info("get_droplet_id_by_name(#{droplet_name}): not found any droplets")
|
29
56
|
nil
|
30
57
|
else
|
31
|
-
|
58
|
+
logger.info("get_droplet_id_by_name(#{droplet_name}): #{droplet.id}")
|
32
59
|
droplet.id
|
33
60
|
end
|
34
61
|
end
|
35
62
|
|
63
|
+
# Return droplet ip by it's name
|
64
|
+
# @param droplet_name [String] name of droplet
|
65
|
+
# @return [String] ip of droplet
|
36
66
|
def get_droplet_ip_by_name(droplet_name)
|
37
67
|
droplet = droplet_by_name(droplet_name)
|
38
68
|
if droplet.nil?
|
39
|
-
|
69
|
+
logger.info("There is no created droplet with name: #{droplet_name}")
|
40
70
|
return
|
41
71
|
end
|
42
72
|
retry_exception do
|
43
|
-
ip = droplet
|
44
|
-
|
73
|
+
ip = public_ip(droplet)
|
74
|
+
logger.info("get_droplet_ip_by_name(#{droplet_name}): #{ip}")
|
45
75
|
ip
|
46
76
|
end
|
47
77
|
end
|
48
78
|
|
79
|
+
# Return droplet status by it's name
|
80
|
+
# @param droplet_name [String] name of droplet
|
81
|
+
# @return [Symbol] droplet status
|
49
82
|
def get_droplet_status_by_name(droplet_name)
|
50
83
|
droplet = droplet_by_name(droplet_name)
|
51
84
|
if droplet.nil?
|
52
|
-
|
85
|
+
logger.info("get_droplet_status_by_name(#{droplet_name}): not found any droplets")
|
53
86
|
nil
|
54
87
|
else
|
55
88
|
retry_exception do
|
56
89
|
status = droplet.status
|
57
90
|
status = :locked if droplet.locked
|
58
|
-
|
91
|
+
logger.info("get_droplet_status_by_name(#{droplet_name}): #{status}")
|
59
92
|
status
|
60
93
|
end
|
61
94
|
end
|
62
95
|
end
|
96
|
+
|
97
|
+
# Get public ip of droplet
|
98
|
+
# @param [DropletKit] droplet to get ip
|
99
|
+
# @return [String] public ip
|
100
|
+
def public_ip(droplet)
|
101
|
+
networks = droplet.networks.to_a.first
|
102
|
+
public_network = networks.find { |net| net.type == 'public' }
|
103
|
+
public_network.ip_address
|
104
|
+
end
|
63
105
|
end
|
64
106
|
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'logger'
|
4
|
+
|
5
|
+
module OnlyofficeDigitaloceanWrapper
|
6
|
+
# Logger module for logging stuff
|
7
|
+
module LoggerWrapper
|
8
|
+
# @return [Logger] default logger
|
9
|
+
def logger
|
10
|
+
@logger ||= Logger.new($stdout)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -3,18 +3,27 @@
|
|
3
3
|
module OnlyofficeDigitaloceanWrapper
|
4
4
|
# Actions with power (turn on/off, reboot)
|
5
5
|
module PowerActions
|
6
|
+
# Turn off droplet
|
7
|
+
# @param droplet_name [String] droplet to turn off
|
8
|
+
# @return [Symbol] droplet result status
|
6
9
|
def power_off_droplet(droplet_name)
|
7
10
|
droplet_id = get_droplet_id_by_name(droplet_name)
|
8
11
|
client.droplet_actions.power_off(droplet_id: droplet_id)
|
9
12
|
wait_until_droplet_have_status(droplet_name, 'off')
|
10
13
|
end
|
11
14
|
|
15
|
+
# Turn on droplet
|
16
|
+
# @param droplet_name [String] droplet to turn on
|
17
|
+
# @return [Symbol] droplet result status
|
12
18
|
def power_on_droplet(droplet_name)
|
13
19
|
droplet_id = get_droplet_id_by_name(droplet_name)
|
14
20
|
client.droplet_actions.power_on(droplet_id: droplet_id)
|
15
21
|
wait_until_droplet_have_status(droplet_name)
|
16
22
|
end
|
17
23
|
|
24
|
+
# Reboot droplet
|
25
|
+
# @param droplet_name [String] droplet to reboot
|
26
|
+
# @return [Symbol] droplet result status
|
18
27
|
def reboot_droplet(droplet_name)
|
19
28
|
droplet_id = get_droplet_id_by_name(droplet_name)
|
20
29
|
client.droplet_actions.reboot(droplet_id: droplet_id)
|
@@ -15,14 +15,17 @@ module OnlyofficeDigitaloceanWrapper
|
|
15
15
|
end
|
16
16
|
|
17
17
|
# Read access token from file system
|
18
|
+
# @param token_file_path [String] path to token
|
19
|
+
# @param force_file_read [True, False] should read from file be forced
|
18
20
|
# @return [String] token
|
19
|
-
def read_token
|
20
|
-
|
21
|
+
def read_token(token_file_path: "#{Dir.home}/.do/access_token",
|
22
|
+
force_file_read: false)
|
23
|
+
return ENV['DO_ACCESS_TOKEN'] if ENV['DO_ACCESS_TOKEN'] && !force_file_read
|
21
24
|
|
22
|
-
File.read(
|
25
|
+
File.read(token_file_path).delete("\n")
|
23
26
|
rescue Errno::ENOENT
|
24
|
-
raise Errno::ENOENT, "No access token found in #{
|
25
|
-
|
27
|
+
raise Errno::ENOENT, "No access token found in #{token_file_path}. " \
|
28
|
+
"Please create file #{token_file_path} with token"
|
26
29
|
end
|
27
30
|
end
|
28
31
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: onlyoffice_digitalocean_wrapper
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.7.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- ONLYOFFICE
|
@@ -10,7 +10,7 @@ authors:
|
|
10
10
|
autorequire:
|
11
11
|
bindir: bin
|
12
12
|
cert_chain: []
|
13
|
-
date:
|
13
|
+
date: 2021-07-29 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: droplet_kit
|
@@ -27,19 +27,33 @@ dependencies:
|
|
27
27
|
- !ruby/object:Gem::Version
|
28
28
|
version: '3'
|
29
29
|
- !ruby/object:Gem::Dependency
|
30
|
-
name:
|
30
|
+
name: codecov
|
31
31
|
requirement: !ruby/object:Gem::Requirement
|
32
32
|
requirements:
|
33
33
|
- - "~>"
|
34
34
|
- !ruby/object:Gem::Version
|
35
|
-
version: '
|
36
|
-
type: :
|
35
|
+
version: '0'
|
36
|
+
type: :development
|
37
37
|
prerelease: false
|
38
38
|
version_requirements: !ruby/object:Gem::Requirement
|
39
39
|
requirements:
|
40
40
|
- - "~>"
|
41
41
|
- !ruby/object:Gem::Version
|
42
|
-
version: '
|
42
|
+
version: '0'
|
43
|
+
- !ruby/object:Gem::Dependency
|
44
|
+
name: overcommit
|
45
|
+
requirement: !ruby/object:Gem::Requirement
|
46
|
+
requirements:
|
47
|
+
- - "~>"
|
48
|
+
- !ruby/object:Gem::Version
|
49
|
+
version: '0'
|
50
|
+
type: :development
|
51
|
+
prerelease: false
|
52
|
+
version_requirements: !ruby/object:Gem::Requirement
|
53
|
+
requirements:
|
54
|
+
- - "~>"
|
55
|
+
- !ruby/object:Gem::Version
|
56
|
+
version: '0'
|
43
57
|
- !ruby/object:Gem::Dependency
|
44
58
|
name: rake
|
45
59
|
requirement: !ruby/object:Gem::Requirement
|
@@ -54,6 +68,90 @@ dependencies:
|
|
54
68
|
- - "~>"
|
55
69
|
- !ruby/object:Gem::Version
|
56
70
|
version: '13.0'
|
71
|
+
- !ruby/object:Gem::Dependency
|
72
|
+
name: rspec
|
73
|
+
requirement: !ruby/object:Gem::Requirement
|
74
|
+
requirements:
|
75
|
+
- - "~>"
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: '3'
|
78
|
+
type: :development
|
79
|
+
prerelease: false
|
80
|
+
version_requirements: !ruby/object:Gem::Requirement
|
81
|
+
requirements:
|
82
|
+
- - "~>"
|
83
|
+
- !ruby/object:Gem::Version
|
84
|
+
version: '3'
|
85
|
+
- !ruby/object:Gem::Dependency
|
86
|
+
name: rubocop
|
87
|
+
requirement: !ruby/object:Gem::Requirement
|
88
|
+
requirements:
|
89
|
+
- - ">="
|
90
|
+
- !ruby/object:Gem::Version
|
91
|
+
version: 0.49.0
|
92
|
+
type: :development
|
93
|
+
prerelease: false
|
94
|
+
version_requirements: !ruby/object:Gem::Requirement
|
95
|
+
requirements:
|
96
|
+
- - ">="
|
97
|
+
- !ruby/object:Gem::Version
|
98
|
+
version: 0.49.0
|
99
|
+
- !ruby/object:Gem::Dependency
|
100
|
+
name: rubocop-performance
|
101
|
+
requirement: !ruby/object:Gem::Requirement
|
102
|
+
requirements:
|
103
|
+
- - "~>"
|
104
|
+
- !ruby/object:Gem::Version
|
105
|
+
version: '1'
|
106
|
+
type: :development
|
107
|
+
prerelease: false
|
108
|
+
version_requirements: !ruby/object:Gem::Requirement
|
109
|
+
requirements:
|
110
|
+
- - "~>"
|
111
|
+
- !ruby/object:Gem::Version
|
112
|
+
version: '1'
|
113
|
+
- !ruby/object:Gem::Dependency
|
114
|
+
name: rubocop-rake
|
115
|
+
requirement: !ruby/object:Gem::Requirement
|
116
|
+
requirements:
|
117
|
+
- - "~>"
|
118
|
+
- !ruby/object:Gem::Version
|
119
|
+
version: '0'
|
120
|
+
type: :development
|
121
|
+
prerelease: false
|
122
|
+
version_requirements: !ruby/object:Gem::Requirement
|
123
|
+
requirements:
|
124
|
+
- - "~>"
|
125
|
+
- !ruby/object:Gem::Version
|
126
|
+
version: '0'
|
127
|
+
- !ruby/object:Gem::Dependency
|
128
|
+
name: rubocop-rspec
|
129
|
+
requirement: !ruby/object:Gem::Requirement
|
130
|
+
requirements:
|
131
|
+
- - "~>"
|
132
|
+
- !ruby/object:Gem::Version
|
133
|
+
version: '2'
|
134
|
+
type: :development
|
135
|
+
prerelease: false
|
136
|
+
version_requirements: !ruby/object:Gem::Requirement
|
137
|
+
requirements:
|
138
|
+
- - "~>"
|
139
|
+
- !ruby/object:Gem::Version
|
140
|
+
version: '2'
|
141
|
+
- !ruby/object:Gem::Dependency
|
142
|
+
name: yard
|
143
|
+
requirement: !ruby/object:Gem::Requirement
|
144
|
+
requirements:
|
145
|
+
- - ">="
|
146
|
+
- !ruby/object:Gem::Version
|
147
|
+
version: 0.9.20
|
148
|
+
type: :development
|
149
|
+
prerelease: false
|
150
|
+
version_requirements: !ruby/object:Gem::Requirement
|
151
|
+
requirements:
|
152
|
+
- - ">="
|
153
|
+
- !ruby/object:Gem::Version
|
154
|
+
version: 0.9.20
|
57
155
|
description: Wrapper gem for DigitalOcean. Use in testing projects
|
58
156
|
email:
|
59
157
|
- shockwavenn@gmail.com
|
@@ -67,18 +165,19 @@ files:
|
|
67
165
|
- lib/onlyoffice_digitalocean_wrapper/digitalocean_wrapper/digitalocean_exceptions.rb
|
68
166
|
- lib/onlyoffice_digitalocean_wrapper/digitalocean_wrapper/exceptions_retryer.rb
|
69
167
|
- lib/onlyoffice_digitalocean_wrapper/digitalocean_wrapper/getters.rb
|
168
|
+
- lib/onlyoffice_digitalocean_wrapper/digitalocean_wrapper/logger_wrapper.rb
|
70
169
|
- lib/onlyoffice_digitalocean_wrapper/digitalocean_wrapper/power_actions.rb
|
71
170
|
- lib/onlyoffice_digitalocean_wrapper/digitalocean_wrapper/token_methods.rb
|
72
171
|
- lib/onlyoffice_digitalocean_wrapper/version.rb
|
73
|
-
homepage: https://github.com/
|
172
|
+
homepage: https://github.com/ONLYOFFICE-QA/onlyoffice_digitalocean_wrapper
|
74
173
|
licenses:
|
75
174
|
- AGPL-3.0
|
76
175
|
metadata:
|
77
|
-
bug_tracker_uri: https://github.com/
|
78
|
-
changelog_uri: https://github.com/
|
176
|
+
bug_tracker_uri: https://github.com/ONLYOFFICE-QA/onlyoffice_digitalocean_wrapper/issues
|
177
|
+
changelog_uri: https://github.com/ONLYOFFICE-QA/onlyoffice_digitalocean_wrapper/blob/master/CHANGELOG.md
|
79
178
|
documentation_uri: https://www.rubydoc.info/gems/onlyoffice_digitalocean_wrapper
|
80
|
-
homepage_uri: https://github.com/
|
81
|
-
source_code_uri: https://github.com/
|
179
|
+
homepage_uri: https://github.com/ONLYOFFICE-QA/onlyoffice_digitalocean_wrapper
|
180
|
+
source_code_uri: https://github.com/ONLYOFFICE-QA/onlyoffice_digitalocean_wrapper
|
82
181
|
post_install_message:
|
83
182
|
rdoc_options: []
|
84
183
|
require_paths:
|
@@ -87,14 +186,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
87
186
|
requirements:
|
88
187
|
- - ">="
|
89
188
|
- !ruby/object:Gem::Version
|
90
|
-
version: '2.
|
189
|
+
version: '2.5'
|
91
190
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
92
191
|
requirements:
|
93
192
|
- - ">="
|
94
193
|
- !ruby/object:Gem::Version
|
95
194
|
version: '0'
|
96
195
|
requirements: []
|
97
|
-
rubygems_version: 3.
|
196
|
+
rubygems_version: 3.2.22
|
98
197
|
signing_key:
|
99
198
|
specification_version: 4
|
100
199
|
summary: Wrapper gem for DigitalOcean
|