uffizzi_core 0.1.11 → 0.1.12

Sign up to get free protection for your applications and to get access to all the features.
Files changed (37) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +6 -0
  3. data/app/controllers/uffizzi_core/api/cli/v1/account/credentials_controller.rb +3 -3
  4. data/app/controllers/uffizzi_core/api/cli/v1/projects_controller.rb +68 -2
  5. data/app/forms/uffizzi_core/api/cli/v1/deployment/create_form.rb +1 -0
  6. data/app/forms/uffizzi_core/api/cli/v1/project/create_form.rb +7 -0
  7. data/app/lib/uffizzi_core/concerns/models/activity_item.rb +39 -0
  8. data/app/lib/uffizzi_core/concerns/models/credential.rb +65 -0
  9. data/app/lib/uffizzi_core/concerns/models/repo.rb +33 -0
  10. data/app/lib/uffizzi_core/rbac/user_access_service.rb +24 -0
  11. data/app/models/uffizzi_core/project.rb +9 -2
  12. data/app/policies/uffizzi_core/api/cli/v1/projects_policy.rb +12 -0
  13. data/app/serializers/uffizzi_core/api/cli/v1/project_serializer/compose_file_serializer.rb +7 -0
  14. data/app/serializers/uffizzi_core/api/cli/v1/project_serializer/deployment_serializer.rb +13 -0
  15. data/app/serializers/uffizzi_core/api/cli/v1/project_serializer.rb +24 -1
  16. data/app/serializers/uffizzi_core/api/cli/v1/projects/deployment_serializer/container_serializer.rb +2 -1
  17. data/app/serializers/uffizzi_core/api/cli/v1/projects/deployments/container_serializer.rb +1 -1
  18. data/app/serializers/uffizzi_core/api/cli/v1/short_project_serializer.rb +7 -0
  19. data/app/serializers/uffizzi_core/controller/deploy_containers/container_serializer.rb +16 -1
  20. data/app/services/uffizzi_core/compose_file/builders/container_builder_service.rb +11 -7
  21. data/app/services/uffizzi_core/compose_file/config_option_service.rb +4 -0
  22. data/app/services/uffizzi_core/compose_file/parsers/services/healthcheck_parser_service.rb +73 -0
  23. data/app/services/uffizzi_core/compose_file/services_options_service.rb +2 -0
  24. data/app/services/uffizzi_core/compose_file/template_service.rb +4 -4
  25. data/app/services/uffizzi_core/compose_file_service.rb +1 -1
  26. data/app/services/uffizzi_core/deployment_service.rb +17 -5
  27. data/app/services/uffizzi_core/manage_activity_items_service.rb +1 -1
  28. data/app/services/uffizzi_core/project_service.rb +10 -0
  29. data/app/services/uffizzi_core/starter_template_service.rb +200 -0
  30. data/app/services/uffizzi_core/user_generator_service.rb +11 -5
  31. data/config/locales/en.yml +2 -0
  32. data/config/routes.rb +11 -1
  33. data/db/migrate/20220419074956_add_health_check_to_containers.rb +7 -0
  34. data/lib/tasks/uffizzi_core_tasks.rake +1 -1
  35. data/lib/uffizzi_core/version.rb +1 -1
  36. data/swagger/v1/swagger.json +174 -2
  37. metadata +12 -2
@@ -8,12 +8,12 @@ module UffizziCore::DeploymentService
8
8
  building: :building,
9
9
  deploying: :deploying,
10
10
  failed: :failed,
11
+ queued: :queued,
11
12
  }.freeze
12
13
 
13
14
  class << self
14
15
  def create_from_compose(compose_file, project, user)
15
16
  deployment_attributes = ActionController::Parameters.new(compose_file.template.payload)
16
-
17
17
  deployment_form = UffizziCore::Api::Cli::V1::Deployment::CreateForm.new(deployment_attributes)
18
18
  deployment_form.assign_dependences!(project, user)
19
19
  deployment_form.compose_file = compose_file
@@ -103,8 +103,9 @@ module UffizziCore::DeploymentService
103
103
  pull_request_payload = continuous_preview_payload['pull_request']
104
104
  repo_name = pull_request_payload['repository_full_name'].split('/').last
105
105
  deployment_name = name(deployment)
106
+ subdomain = "pr#{pull_request_payload['id']}-#{deployment_name}-#{repo_name}-#{project.slug}"
106
107
 
107
- "pr#{pull_request_payload['id']}-#{deployment_name}.#{repo_name}.#{project.slug}"
108
+ format_subdomain(subdomain)
108
109
  end
109
110
 
110
111
  def build_docker_continuous_preview_subdomain(deployment)
@@ -114,14 +115,17 @@ module UffizziCore::DeploymentService
114
115
  repo_name = docker_payload['image'].split('/').last.gsub('_', '-')
115
116
  image_tag = docker_payload['tag'].gsub('_', '-')
116
117
  deployment_name = name(deployment)
118
+ subdomain = "#{image_tag}-#{deployment_name}-#{repo_name}-#{project.slug}"
117
119
 
118
- "#{image_tag}-#{deployment_name}.#{repo_name}.#{project.slug}"
120
+ format_subdomain(subdomain)
119
121
  end
120
122
 
121
123
  def build_default_subdomain(deployment)
122
124
  deployment_name = name(deployment)
123
125
  slug = deployment.project.slug.to_s
124
- "#{deployment_name}.#{slug}"
126
+ subdomain = "#{deployment_name}-#{slug}"
127
+
128
+ format_subdomain(subdomain)
125
129
  end
126
130
 
127
131
  def build_preview_url(deployment)
@@ -238,10 +242,11 @@ module UffizziCore::DeploymentService
238
242
 
239
243
  def deployment_process_status(deployment)
240
244
  containers = deployment.active_containers
241
- activity_items = containers.map { |container| container.activity_items.order_by_id.last }
245
+ activity_items = containers.map { |container| container.activity_items.order_by_id.last }.compact
242
246
  events = activity_items.map { |activity_item| activity_item.events.order_by_id.last&.state }
243
247
  events = events.flatten.uniq
244
248
 
249
+ return DEPLOYMENT_PROCESS_STATUSES[:queued] if containers.present? && events.empty?
245
250
  return DEPLOYMENT_PROCESS_STATUSES[:failed] if events.include?(UffizziCore::Event.state.failed)
246
251
  return DEPLOYMENT_PROCESS_STATUSES[:building] if events.include?(UffizziCore::Event.state.building)
247
252
 
@@ -291,5 +296,12 @@ module UffizziCore::DeploymentService
291
296
  container.variables.push(*envs)
292
297
  end
293
298
  end
299
+
300
+ def format_subdomain(full_subdomain_name)
301
+ subdomain_length_limit = Settings.deployment.subdomain.length_limit
302
+ return full_subdomain_name if full_subdomain_name.length <= subdomain_length_limit
303
+
304
+ full_subdomain_name.slice(0, subdomain_length_limit)
305
+ end
294
306
  end
295
307
  end
@@ -118,7 +118,7 @@ class UffizziCore::ManageActivityItemsService
118
118
 
119
119
  Rails.logger.info("manage_activity_items get_status dep_id=#{container.deployment.id} pod_container_status: #{pod_container_status}")
120
120
  Rails.logger.info("manage_activity_items get_status dep_id=#{container.deployment.id} state: #{state}")
121
- ap pod_container[:state]
121
+ ap(pod_container[:state])
122
122
 
123
123
  case pod_container_status.to_sym
124
124
  when :running
@@ -34,5 +34,15 @@ module UffizziCore::ProjectService
34
34
 
35
35
  UffizziCore::ComposeFile::ErrorsService.update_compose_errors!(compose_file, new_errors, compose_file.content)
36
36
  end
37
+
38
+ def add_users_to_project!(project, current_user)
39
+ user_projects = []
40
+
41
+ current_user.organizational_account.memberships.where(role: UffizziCore::Membership.role.admin).map do |membership|
42
+ user_projects << { project: project, user: membership.user, role: UffizziCore::UserProject.role.admin }
43
+ end
44
+
45
+ UffizziCore::UserProject.create!(user_projects)
46
+ end
37
47
  end
38
48
  end
@@ -0,0 +1,200 @@
1
+ # frozen_string_literal: true
2
+
3
+ class UffizziCore::StarterTemplateService
4
+ class << self
5
+ def create(project, user)
6
+ voting_app_config_file = create_config_file(project, user)
7
+ create_containers(project, user, voting_app_config_file)
8
+ end
9
+
10
+ private
11
+
12
+ def create_config_file(project, user)
13
+ starter_template_config_file = config_file_attributes
14
+
15
+ starter_template_config_file_params = ActionController::Parameters.new(starter_template_config_file)
16
+
17
+ starter_template_config_file_form = UffizziCore::Api::Cli::V1::ConfigFile::CreateForm.new(starter_template_config_file_params)
18
+ starter_template_config_file_form.project = project
19
+ starter_template_config_file_form.added_by = user
20
+ starter_template_config_file_form.creation_source = UffizziCore::ConfigFile.creation_source.system
21
+ starter_template_config_file_form.save
22
+
23
+ starter_template_config_file_form
24
+ end
25
+
26
+ def create_containers(project, user, voting_app_config_file)
27
+ starter_template_containers = {
28
+ name: 'Voting App (from base images)',
29
+ payload: {
30
+ containers_attributes: containers_attributes(voting_app_config_file),
31
+ },
32
+ }
33
+
34
+ starter_template = ActionController::Parameters.new(starter_template_containers)
35
+
36
+ starter_template_form = UffizziCore::Api::Cli::V1::Template::CreateForm.new(starter_template)
37
+ starter_template_form.creation_source = UffizziCore::Template.creation_source.system
38
+ starter_template_form.added_by = user
39
+ starter_template_form.project = project
40
+ starter_template_form.save
41
+ end
42
+
43
+ def containers_attributes(voting_app_config_file)
44
+ [
45
+ postgres_container_attributes,
46
+ nginx_container_attributes(voting_app_config_file),
47
+ redis_container_attributes,
48
+ example_worker_container_attributes,
49
+ example_result_container_attributes,
50
+ example_vote_container_attributes,
51
+ ]
52
+ end
53
+
54
+ def postgres_container_attributes
55
+ {
56
+ image: 'library/postgres',
57
+ tag: '9.6',
58
+ port: nil,
59
+ public: false,
60
+ memory_limit: 250,
61
+ memory_request: 250,
62
+ receive_incoming_requests: false,
63
+ continuously_deploy: 'disabled',
64
+ secret_variables: nil,
65
+ variables: [
66
+ {
67
+ name: 'POSTGRES_USER',
68
+ value: 'postgres',
69
+ },
70
+ {
71
+ name: 'POSTGRES_PASSWORD',
72
+ value: 'postgres',
73
+ },
74
+ ],
75
+ repo_attributes: repo_attributes('library/postgres'),
76
+ container_config_files_attributes: [],
77
+ }
78
+ end
79
+
80
+ def nginx_container_attributes(voting_app_config_file)
81
+ {
82
+ image: 'library/nginx',
83
+ tag: 'latest',
84
+ port: 8080,
85
+ public: true,
86
+ memory_limit: 125,
87
+ memory_request: 125,
88
+ receive_incoming_requests: true,
89
+ continuously_deploy: 'disabled',
90
+ secret_variables: nil,
91
+ variables: nil,
92
+ repo_attributes: repo_attributes('library/nginx'),
93
+ container_config_files_attributes: [{
94
+ config_file_id: voting_app_config_file.id,
95
+ mount_path: '/etc/nginx/conf.d/',
96
+ }],
97
+ }
98
+ end
99
+
100
+ def redis_container_attributes
101
+ {
102
+ image: 'library/redis',
103
+ tag: 'latest',
104
+ port: nil,
105
+ public: false,
106
+ memory_limit: 125,
107
+ memory_request: 125,
108
+ receive_incoming_requests: false,
109
+ continuously_deploy: 'disabled',
110
+ secret_variables: nil,
111
+ variables: nil,
112
+ repo_attributes: repo_attributes('library/redis'),
113
+ container_config_files_attributes: [],
114
+ }
115
+ end
116
+
117
+ def example_worker_container_attributes
118
+ {
119
+ image: 'uffizzicloud/example-worker',
120
+ tag: 'latest',
121
+ port: nil,
122
+ public: false,
123
+ memory_limit: 250,
124
+ memory_request: 250,
125
+ receive_incoming_requests: false,
126
+ continuously_deploy: 'disabled',
127
+ secret_variables: nil,
128
+ variables: nil,
129
+ repo_attributes: repo_attributes('uffizzicloud/example-worker'),
130
+ container_config_files_attributes: [],
131
+ }
132
+ end
133
+
134
+ def example_result_container_attributes
135
+ {
136
+ image: 'uffizzicloud/example-result',
137
+ tag: 'latest',
138
+ port: nil,
139
+ public: false,
140
+ memory_limit: 125,
141
+ memory_request: 125,
142
+ receive_incoming_requests: false,
143
+ continuously_deploy: 'disabled',
144
+ secret_variables: nil,
145
+ variables: nil,
146
+ repo_attributes: repo_attributes('uffizzicloud/example-result'),
147
+ container_config_files_attributes: [],
148
+ }
149
+ end
150
+
151
+ def example_vote_container_attributes
152
+ {
153
+ image: 'uffizzicloud/example-vote',
154
+ tag: 'latest',
155
+ port: nil,
156
+ public: false,
157
+ memory_limit: 250,
158
+ memory_request: 250,
159
+ receive_incoming_requests: false,
160
+ continuously_deploy: 'disabled',
161
+ secret_variables: nil,
162
+ variables: nil,
163
+ repo_attributes: repo_attributes('uffizzicloud/example-vote'),
164
+ container_config_files_attributes: [],
165
+ }
166
+ end
167
+
168
+ def config_file_attributes
169
+ {
170
+ filename: 'vote.conf',
171
+ kind: 'config_map',
172
+ payload: "server {
173
+ listen 8080;
174
+ server_name example.com;
175
+ location / {
176
+ proxy_pass http://127.0.0.1:80/;
177
+ }
178
+ location /vote/ {
179
+ proxy_pass http://127.0.0.1:8888/;
180
+ }
181
+ }",
182
+ }
183
+ end
184
+
185
+ def repo_attributes(image)
186
+ namespace, name = image.split('/')
187
+ {
188
+ namespace: namespace,
189
+ name: name,
190
+ slug: name,
191
+ type: 'UffizziCore::Repo::DockerHub',
192
+ description: '',
193
+ is_private: false,
194
+ repository_id: nil,
195
+ branch: '',
196
+ kind: nil,
197
+ }
198
+ end
199
+ end
200
+ end
@@ -6,6 +6,12 @@ class UffizziCore::UserGeneratorService
6
6
  DEFAULT_ACCOUNT_NAME = 'default'
7
7
 
8
8
  class << self
9
+ def safe_generate(email, password, project_name)
10
+ generate(email, password, project_name)
11
+ rescue StandardError => e
12
+ puts e.message
13
+ end
14
+
9
15
  def generate(email, password, project_name)
10
16
  user_attributes = build_user_attributes(email, password)
11
17
  project_attributes = build_project_attributes(project_name)
@@ -31,19 +37,17 @@ class UffizziCore::UserGeneratorService
31
37
 
32
38
  if email.present?
33
39
  user_attributes[:email] = email
34
- else
40
+ elsif IO::console.present?
35
41
  IO::console.write("Enter User Email (default: #{DEFAULT_USER_EMAIL}): ")
36
42
  user_attributes[:email] = IO::console.gets.strip.presence || DEFAULT_USER_EMAIL
37
43
  end
38
44
 
39
45
  user_attributes[:password] = if password.present?
40
46
  password
41
- else
47
+ elsif IO::console.present?
42
48
  IO::console.getpass('Enter Password: ')
43
49
  end
44
50
 
45
- abort('password can\'t be blank') if user_attributes[:password].blank?
46
-
47
51
  user_attributes
48
52
  end
49
53
 
@@ -53,9 +57,11 @@ class UffizziCore::UserGeneratorService
53
57
  }
54
58
  if project_name.present?
55
59
  project_attributes[:name] = project_name
56
- else
60
+ elsif IO::console.present?
57
61
  IO::console.write("Enter Project Name (default: #{DEFAULT_PROJECT_NAME}): ")
58
62
  project_attributes[:name] = IO::console.gets.strip.presence || DEFAULT_PROJECT_NAME
63
+ else
64
+ project_attributes[:name] = DEFAULT_PROJECT_NAME
59
65
  end
60
66
 
61
67
  project_attributes[:slug] = prepare_project_slug(project_attributes[:name])
@@ -57,8 +57,10 @@ en:
57
57
  continuous_preview_in_service_level: The option '%{option}' is not supported for service-level. Use 'x-uffizzi-continuous-preview' instead
58
58
  file_already_exists: A compose file already exists for this project. Run 'uffizzi compose update' to update this file or 'uffizzi compose rm' to remove it. For more options, see 'uffizzi compose --help'
59
59
  invalid_healthcheck_command: "Service '%{name}' defines an invalid healthcheck: when 'test' is a list the first item must be either NONE, CMD or CMD-SHELL"
60
+ invalid_time_interval: The time interval should be in the following format '{hours}h{minutes}m{seconds}s'. At least one value must be present.
60
61
  string_or_array_error: "'%{option}' contains an invalid type, it should be a string, or an array"
61
62
  not_implemented: "'%{option}' option is not implemented"
63
+ infinite_recursion: "Found infinite recursion for key '%{key}'"
62
64
  secrets:
63
65
  duplicates_exists: Secret with key %{secrets} already exist.
64
66
  invalid_key_length: A secret key must be no longer than 256 characters.
data/config/routes.rb CHANGED
@@ -15,13 +15,23 @@ UffizziCore::Engine.routes.draw do
15
15
  post :google
16
16
  end
17
17
 
18
- resources :projects, only: ['index'], param: :slug do
18
+ resources :projects, only: ['index', 'show', 'create', 'destroy'], param: :slug do
19
19
  scope module: :projects do
20
20
  resource :compose_file, only: ['show', 'create', 'destroy']
21
21
  resources :deployments, only: ['index', 'show', 'create', 'destroy', 'update'] do
22
22
  post :deploy_containers, on: :member
23
23
  scope module: :deployments do
24
24
  resources :activity_items, only: ['index']
25
+ resources :containers, only: ['index'], param: :name do
26
+ scope module: :containers do
27
+ resources :logs, only: ['index']
28
+ resources :builds, only: [] do
29
+ collection do
30
+ get :logs
31
+ end
32
+ end
33
+ end
34
+ end
25
35
  resources :events, only: ['index']
26
36
  resources :containers, only: ['index'], param: :name do
27
37
  scope module: :containers do
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ class AddHealthCheckToContainers < ActiveRecord::Migration[6.1]
4
+ def change
5
+ add_column :uffizzi_core_containers, :healthcheck, :jsonb
6
+ end
7
+ end
@@ -14,6 +14,6 @@ namespace :uffizzi_core do
14
14
 
15
15
  desc 'Create a new user'
16
16
  task create_user: :environment do
17
- UffizziCore::UserGeneratorService.generate(ENV['UFFIZZI_USER_EMAIL'], ENV['UFFIZZI_USER_PASSWORD'], ENV['UFFIZZI_PROJECT_NAME'])
17
+ UffizziCore::UserGeneratorService.safe_generate(ENV['UFFIZZI_USER_EMAIL'], ENV['UFFIZZI_USER_PASSWORD'], ENV['UFFIZZI_PROJECT_NAME'])
18
18
  end
19
19
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module UffizziCore
4
- VERSION = '0.1.11'
4
+ VERSION = '0.1.12'
5
5
  end
@@ -80,7 +80,7 @@
80
80
  "default": {
81
81
  "description": "Create account credential",
82
82
  "examples": {
83
- "application/json": "type can be one of UffizziCore::Credential::Amazon, UffizziCore::Credential::Azure, UffizziCore::Credential::DockerHub, UffizziCore::Credential::Google, UffizziCore::Credential::GithubContainerRegistry"
83
+ "application/json": "type can be one of UffizziCore::Credential::Amazon, UffizziCore::Credential::Azure,\nUffizziCore::Credential::DockerHub, UffizziCore::Credential::Google, UffizziCore::Credential::GithubContainerRegistry"
84
84
  }
85
85
  },
86
86
  "201": {
@@ -1134,6 +1134,9 @@
1134
1134
  "properties": {
1135
1135
  "slug": {
1136
1136
  "type": "string"
1137
+ },
1138
+ "name": {
1139
+ "type": "string"
1137
1140
  }
1138
1141
  }
1139
1142
  }
@@ -1149,6 +1152,143 @@
1149
1152
  "summary": "Get projects of current user",
1150
1153
  "x-controller": "uffizzi_core/api/cli/v1/projects",
1151
1154
  "x-action": "index"
1155
+ },
1156
+ "post": {
1157
+ "tags": [
1158
+ "Project"
1159
+ ],
1160
+ "operationId": "Project-create",
1161
+ "parameters": [
1162
+ {
1163
+ "name": "params",
1164
+ "description": "params",
1165
+ "required": true,
1166
+ "in": "body",
1167
+ "schema": {
1168
+ "type": "object",
1169
+ "properties": {
1170
+ "name": {
1171
+ "type": "string"
1172
+ },
1173
+ "slug": {
1174
+ "type": "string"
1175
+ },
1176
+ "description": {
1177
+ "type": "string"
1178
+ }
1179
+ }
1180
+ }
1181
+ }
1182
+ ],
1183
+ "responses": {
1184
+ "200": {
1185
+ "description": "OK",
1186
+ "schema": {
1187
+ "type": "object",
1188
+ "properties": {
1189
+ "project": {
1190
+ "$ref": "#/definitions/Project"
1191
+ }
1192
+ }
1193
+ }
1194
+ },
1195
+ "404": {
1196
+ "description": "Not Found"
1197
+ },
1198
+ "401": {
1199
+ "description": "Not authorized"
1200
+ },
1201
+ "422": {
1202
+ "description": "Unprocessable entity",
1203
+ "schema": {
1204
+ "type": "object",
1205
+ "properties": {
1206
+ "errors": {
1207
+ "type": "object",
1208
+ "properties": {
1209
+ "password": {
1210
+ "type": "string"
1211
+ }
1212
+ }
1213
+ }
1214
+ }
1215
+ }
1216
+ }
1217
+ },
1218
+ "description": "Create a project",
1219
+ "summary": "Create a project",
1220
+ "x-controller": "uffizzi_core/api/cli/v1/projects",
1221
+ "x-action": "create"
1222
+ }
1223
+ },
1224
+ "/api/cli/v1/projects/{slug}": {
1225
+ "get": {
1226
+ "tags": [
1227
+ "Project"
1228
+ ],
1229
+ "operationId": "Project-show",
1230
+ "parameters": [
1231
+ {
1232
+ "name": "slug",
1233
+ "description": "Scope response to slug",
1234
+ "required": true,
1235
+ "in": "path",
1236
+ "type": "string"
1237
+ }
1238
+ ],
1239
+ "responses": {
1240
+ "200": {
1241
+ "description": "OK",
1242
+ "schema": {
1243
+ "type": "object",
1244
+ "properties": {
1245
+ "project": {
1246
+ "$ref": "#/definitions/Project"
1247
+ }
1248
+ }
1249
+ }
1250
+ },
1251
+ "404": {
1252
+ "description": "Not Found"
1253
+ },
1254
+ "401": {
1255
+ "description": "Not authorized"
1256
+ }
1257
+ },
1258
+ "description": "Get a project by slug",
1259
+ "summary": "Get a project by slug",
1260
+ "x-controller": "uffizzi_core/api/cli/v1/projects",
1261
+ "x-action": "show"
1262
+ },
1263
+ "delete": {
1264
+ "tags": [
1265
+ "Project"
1266
+ ],
1267
+ "operationId": "Project-destroy",
1268
+ "parameters": [
1269
+ {
1270
+ "name": "slug",
1271
+ "description": "Scope response to slug",
1272
+ "required": true,
1273
+ "in": "path",
1274
+ "type": "string"
1275
+ }
1276
+ ],
1277
+ "responses": {
1278
+ "204": {
1279
+ "description": "No content"
1280
+ },
1281
+ "404": {
1282
+ "description": "Not Found"
1283
+ },
1284
+ "401": {
1285
+ "description": "Not authorized"
1286
+ }
1287
+ },
1288
+ "description": "Delete a project",
1289
+ "summary": "Delete a project",
1290
+ "x-controller": "uffizzi_core/api/cli/v1/projects",
1291
+ "x-action": "destroy"
1152
1292
  }
1153
1293
  },
1154
1294
  "/api/cli/v1/session": {
@@ -1475,11 +1615,43 @@
1475
1615
  }
1476
1616
  }
1477
1617
  },
1478
- "UffizziCore_Project": {
1618
+ "Project": {
1479
1619
  "type": "object",
1480
1620
  "properties": {
1481
1621
  "slug": {
1482
1622
  "type": "string"
1623
+ },
1624
+ "name": {
1625
+ "type": "string"
1626
+ },
1627
+ "description": {
1628
+ "type": "string"
1629
+ },
1630
+ "created_at": {
1631
+ "type": "string",
1632
+ "format": "date-time"
1633
+ },
1634
+ "secrets": {
1635
+ "type": "string"
1636
+ },
1637
+ "default_compose": {
1638
+ "type": "object",
1639
+ "properties": {
1640
+ "source": {
1641
+ "type": "string"
1642
+ }
1643
+ }
1644
+ },
1645
+ "deployments": {
1646
+ "type": "object",
1647
+ "properties": {
1648
+ "id": {
1649
+ "type": "integer"
1650
+ },
1651
+ "domain": {
1652
+ "type": "string"
1653
+ }
1654
+ }
1483
1655
  }
1484
1656
  }
1485
1657
  }