diff --git a/Gemfile b/Gemfile
index ecc2da6f0547b3c213ece8df485451b5c2d3853f..32d64b19f7cee253edc1821f104d510dcb52853c 100644
--- a/Gemfile
+++ b/Gemfile
@@ -587,6 +587,9 @@ gem 'cvss-suite', '~> 3.0.1', require: 'cvss_suite'
# Work with RPM packages
gem 'arr-pm', '~> 0.0.12'
+# Remote Development
+gem 'devfile', '~> 0.0.15.pre.alpha1'
+
# Apple plist parsing
gem 'CFPropertyList', '~> 3.0.0'
gem 'app_store_connect'
diff --git a/Gemfile.checksum b/Gemfile.checksum
index 69b15f3b3688b38f57fdd2ecb2a881704bc691b8..d260ad61dcd4c98f57ceec7d6e5ad1c3cc7fa49b 100644
--- a/Gemfile.checksum
+++ b/Gemfile.checksum
@@ -110,6 +110,9 @@
{"name":"deprecation_toolkit","version":"1.5.1","platform":"ruby","checksum":"a8a1ab1a19ae40ea12560b65010e099f3459ebde390b76621ef0c21c516a04ba"},
{"name":"derailed_benchmarks","version":"2.1.2","platform":"ruby","checksum":"eaadc6206ceeb5538ff8f5e04a0023d54ebdd95d04f33e8960fb95a5f189a14f"},
{"name":"descendants_tracker","version":"0.0.4","platform":"ruby","checksum":"e9c41dd4cfbb85829a9301ea7e7c48c2a03b26f09319db230e6479ccdc780897"},
+{"name":"devfile","version":"0.0.15.pre.alpha1","platform":"arm64-darwin","checksum":"8e8234d552310925758dd5f214e1fc059ecc7255f8d692300d1adb955c370198"},
+{"name":"devfile","version":"0.0.15.pre.alpha1","platform":"ruby","checksum":"69a058d9edb44efe2fb78769e531b324f3bd75b21d45be15d8b0335b691d093e"},
+{"name":"devfile","version":"0.0.15.pre.alpha1","platform":"x86_64-linux","checksum":"460ddad57cc69a293661bb82ebc19ca44147201c74f78b36bb4861b402ecff24"},
{"name":"device_detector","version":"1.0.0","platform":"ruby","checksum":"b800fb3150b00c23e87b6768011808ac1771fffaae74c3238ebaf2b782947a7d"},
{"name":"devise","version":"4.8.1","platform":"ruby","checksum":"fdd48bbe79a89e7c1152236a70479842ede48bea4fa7f4f2d8da1f872559803e"},
{"name":"devise-two-factor","version":"4.0.2","platform":"ruby","checksum":"6548d2696ed090d27046f888f4fa7380f151e0f823902d46fd9b91e7d0cac511"},
diff --git a/Gemfile.lock b/Gemfile.lock
index 2d2f2fa783845f114446a861ddf375c634036b75..1ca62011875b7105a2e9d6e9835fbec25637724a 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -376,6 +376,7 @@ GEM
thor (>= 0.19, < 2)
descendants_tracker (0.0.4)
thread_safe (~> 0.3, >= 0.3.1)
+ devfile (0.0.15.pre.alpha1)
device_detector (1.0.0)
devise (4.8.1)
bcrypt (~> 3.0)
@@ -1713,6 +1714,7 @@ DEPENDENCIES
declarative_policy (~> 1.1.0)
deprecation_toolkit (~> 1.5.1)
derailed_benchmarks
+ devfile (~> 0.0.15.pre.alpha1)
device_detector
devise (~> 4.8.1)
devise-pbkdf2-encryptable (~> 0.0.0)!
diff --git a/app/assets/javascripts/graphql_shared/constants.js b/app/assets/javascripts/graphql_shared/constants.js
index 5ba4669749603dfc00db00380ee8a9fd8c46cb7b..0642f157b385acbaef37f4338f1ab6b3b6579a12 100644
--- a/app/assets/javascripts/graphql_shared/constants.js
+++ b/app/assets/javascripts/graphql_shared/constants.js
@@ -27,5 +27,6 @@ export const TYPENAME_USER = 'User';
export const TYPENAME_VULNERABILITIES_SCANNER = 'Vulnerabilities::Scanner';
export const TYPENAME_VULNERABILITY = 'Vulnerability';
export const TYPENAME_WORK_ITEM = 'WorkItem';
+export const TYPE_CLUSTERS_AGENT = 'Clusters::Agent';
export const TYPE_USERS_SAVED_REPLY = 'Users::SavedReply';
export const TYPE_WORKSPACE = 'RemoteDevelopment::Workspace';
diff --git a/app/assets/stylesheets/utilities.scss b/app/assets/stylesheets/utilities.scss
index 267bd3f9506a37f9dc7e9d3e080355e18f176d56..08c4efce542ea172624f595ced9203731d8737b2 100644
--- a/app/assets/stylesheets/utilities.scss
+++ b/app/assets/stylesheets/utilities.scss
@@ -149,3 +149,7 @@
.gl-fill-orange-500 {
fill: $orange-500;
}
+
+.gl-fill-red-500 {
+ fill: $red-500;
+}
diff --git a/app/graphql/types/user_interface.rb b/app/graphql/types/user_interface.rb
index 83d2f3f830a81c9962b6a8390a0a52fa2a356b8b..64fc069b508950b86b24bf3071ac5fe299268031 100644
--- a/app/graphql/types/user_interface.rb
+++ b/app/graphql/types/user_interface.rb
@@ -175,3 +175,5 @@ def redacted_name
end
end
end
+
+Types::UserInterface.prepend_mod
diff --git a/db/docs/remote_development_agent_configs.yml b/db/docs/remote_development_agent_configs.yml
new file mode 100644
index 0000000000000000000000000000000000000000..0de90d1777caf5653a0c63738116599bdb39f325
--- /dev/null
+++ b/db/docs/remote_development_agent_configs.yml
@@ -0,0 +1,10 @@
+---
+table_name: remote_development_agent_configs
+classes:
+- RemoteDevelopment::RemoteDevelopmentAgentConfig
+feature_categories:
+- remote_development
+description: Remote Development Cluster Agent Configuration
+introduced_by_url:
+milestone:
+gitlab_schema: gitlab_main
diff --git a/db/docs/workspaces.yml b/db/docs/workspaces.yml
new file mode 100644
index 0000000000000000000000000000000000000000..e9983416e3181abe19c77af9aebbe942538aabd4
--- /dev/null
+++ b/db/docs/workspaces.yml
@@ -0,0 +1,10 @@
+---
+table_name: workspaces
+classes:
+- RemoteDevelopment::Workspace
+feature_categories:
+- remote_development
+description: Remote Development Workspaces
+introduced_by_url:
+milestone:
+gitlab_schema: gitlab_main
diff --git a/db/migrate/20221225010101_create_workspaces_table.rb b/db/migrate/20221225010101_create_workspaces_table.rb
new file mode 100644
index 0000000000000000000000000000000000000000..4c8bc26bcf651fbb82559e2781b6711377f0e5e7
--- /dev/null
+++ b/db/migrate/20221225010101_create_workspaces_table.rb
@@ -0,0 +1,44 @@
+# frozen_string_literal: true
+
+class CreateWorkspacesTable < Gitlab::Database::Migration[2.1]
+ def up
+ create_table :workspaces do |t|
+ t.timestamps_with_timezone null: false
+ # NOTE: All workspace foreign key references are currently `on_delete: :cascade`, because we have no support or
+ # testing around null values. However, in the future we may want to switch these to nullify, especially
+ # once we start introducing logging, metrics, billing, etc. around workspaces.
+ t.bigint :user_id, null: false, index: true
+ t.bigint :project_id, null: false, index: true
+ t.bigint :cluster_agent_id, null: false, index: true
+ t.datetime_with_timezone :desired_state_updated_at, null: false
+ t.datetime_with_timezone :responded_to_agent_at
+ t.integer :max_hours_before_termination, limit: 2, null: false
+ t.text :name, limit: 64, null: false, index: { unique: true }
+ t.text :namespace, limit: 64, null: false
+ t.text :desired_state, limit: 32, null: false
+ t.text :actual_state, limit: 32, null: false
+ t.text :editor, limit: 256, null: false
+ t.text :devfile_ref, limit: 256, null: false
+ t.text :devfile_path, limit: 2048, null: false
+ # NOTE: The limit on the devfile fields are arbitrary, and only added to avoid a rubocop
+ # Migration/AddLimitToTextColumns error. We expect the average devfile side to be small, perhaps ~0.5k for a
+ # devfile and ~2k for a processed_devfile, but to account for unexpected usage resulting in larger files,
+ # we have specified 65535, which allows for a YAML file with over 800 lines of an average 80-character
+ # length.
+ t.text :devfile, limit: 65535
+ t.text :processed_devfile, limit: 65535
+ t.text :url, limit: 1024, null: false
+ # NOTE: The resource version is currently backed by etcd's mod_revision.
+ # However, it's important to note that the application should not rely on the implementation details of
+ # the versioning system maintained by Kubernetes. We may change the implementation of resource version
+ # in the future, such as to change it to a timestamp or per-object counter.
+ # https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#concurrency-control-and-consistency
+ # The limit of 64 is arbitrary.
+ t.text :deployment_resource_version, limit: 64
+ end
+ end
+
+ def down
+ drop_table :workspaces
+ end
+end
diff --git a/db/migrate/20221225010102_create_workspaces_user_foreign_key.rb b/db/migrate/20221225010102_create_workspaces_user_foreign_key.rb
new file mode 100644
index 0000000000000000000000000000000000000000..f6c38f289d62f42cc4fcc7ba4af415c3a452f382
--- /dev/null
+++ b/db/migrate/20221225010102_create_workspaces_user_foreign_key.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+
+class CreateWorkspacesUserForeignKey < Gitlab::Database::Migration[2.1]
+ disable_ddl_transaction!
+
+ def up
+ # NOTE: All workspace foreign key references are currently `on_delete: :cascade`, because we have no support or
+ # testing around null values. However, in the future we may want to switch these to nullify, especially
+ # once we start introducing logging, metrics, billing, etc. around workspaces.
+ add_concurrent_foreign_key :workspaces, :users, column: :user_id, on_delete: :cascade
+ end
+
+ def down
+ with_lock_retries do
+ remove_foreign_key :workspaces, column: :user_id
+ end
+ end
+end
diff --git a/db/migrate/20221225010103_create_workspaces_project_foreign_key.rb b/db/migrate/20221225010103_create_workspaces_project_foreign_key.rb
new file mode 100644
index 0000000000000000000000000000000000000000..fe2b6eec2e01edee81584aba3aa7e4d4aa5b0351
--- /dev/null
+++ b/db/migrate/20221225010103_create_workspaces_project_foreign_key.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+
+class CreateWorkspacesProjectForeignKey < Gitlab::Database::Migration[2.1]
+ disable_ddl_transaction!
+
+ def up
+ # NOTE: All workspace foreign key references are currently `on_delete: :cascade`, because we have no support or
+ # testing around null values. However, in the future we may want to switch these to nullify, especially
+ # once we start introducing logging, metrics, billing, etc. around workspaces.
+ add_concurrent_foreign_key :workspaces, :projects, column: :project_id, on_delete: :cascade
+ end
+
+ def down
+ with_lock_retries do
+ remove_foreign_key :workspaces, column: :project_id
+ end
+ end
+end
diff --git a/db/migrate/20221225010104_create_workspaces_cluster_agent_foreign_key.rb b/db/migrate/20221225010104_create_workspaces_cluster_agent_foreign_key.rb
new file mode 100644
index 0000000000000000000000000000000000000000..c7874349e861d93335c7b41b1404ffda0ddf71fd
--- /dev/null
+++ b/db/migrate/20221225010104_create_workspaces_cluster_agent_foreign_key.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+
+class CreateWorkspacesClusterAgentForeignKey < Gitlab::Database::Migration[2.1]
+ disable_ddl_transaction!
+
+ def up
+ # NOTE: All workspace foreign key references are currently `on_delete: :cascade`, because we have no support or
+ # testing around null values. However, in the future we may want to switch these to nullify, especially
+ # once we start introducing logging, metrics, billing, etc. around workspaces.
+ add_concurrent_foreign_key :workspaces, :cluster_agents, column: :cluster_agent_id, on_delete: :cascade
+ end
+
+ def down
+ with_lock_retries do
+ remove_foreign_key :workspaces, column: :cluster_agent_id
+ end
+ end
+end
diff --git a/db/migrate/20221225010105_create_remote_development_agent_configs_table.rb b/db/migrate/20221225010105_create_remote_development_agent_configs_table.rb
new file mode 100644
index 0000000000000000000000000000000000000000..f375f78b616099cfcc31339d184e4b525f58ed53
--- /dev/null
+++ b/db/migrate/20221225010105_create_remote_development_agent_configs_table.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+
+class CreateRemoteDevelopmentAgentConfigsTable < Gitlab::Database::Migration[2.1]
+ def up
+ create_table :remote_development_agent_configs do |t|
+ t.timestamps_with_timezone null: false
+ t.bigint :cluster_agent_id, null: false, index: true
+ t.boolean :enabled, null: false
+ t.text :dns_zone, null: false, limit: 256
+ end
+ end
+
+ def down
+ drop_table :remote_development_agent_configs
+ end
+end
diff --git a/db/migrate/20221225010106_create_remote_development_agent_config_agent_foreign_key.rb b/db/migrate/20221225010106_create_remote_development_agent_config_agent_foreign_key.rb
new file mode 100644
index 0000000000000000000000000000000000000000..b861f41716847824f541bb4716d7f2a89bf0397f
--- /dev/null
+++ b/db/migrate/20221225010106_create_remote_development_agent_config_agent_foreign_key.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+
+class CreateRemoteDevelopmentAgentConfigAgentForeignKey < Gitlab::Database::Migration[2.1]
+ disable_ddl_transaction!
+
+ def up
+ add_concurrent_foreign_key :remote_development_agent_configs,
+ :cluster_agents, column: :cluster_agent_id, on_delete: :cascade
+ end
+
+ def down
+ with_lock_retries do
+ remove_foreign_key :remote_development_agent_configs, column: :cluster_agent_id
+ end
+ end
+end
diff --git a/db/schema_migrations/20221225010101 b/db/schema_migrations/20221225010101
new file mode 100644
index 0000000000000000000000000000000000000000..62d2d001438b771113c5af5a10eacca6091f70ff
--- /dev/null
+++ b/db/schema_migrations/20221225010101
@@ -0,0 +1 @@
+94810a223f2d37a673d690ba326577068c18d6353021a78a8f820cf8a95c756c
\ No newline at end of file
diff --git a/db/schema_migrations/20221225010102 b/db/schema_migrations/20221225010102
new file mode 100644
index 0000000000000000000000000000000000000000..8aacd082afcd12aabd447f86f6dfbd36029f5f38
--- /dev/null
+++ b/db/schema_migrations/20221225010102
@@ -0,0 +1 @@
+74a3b48267b16dcd9d3374b01604a0ae7f55dd35e681e3bf6bf5386ea4f6bdc3
\ No newline at end of file
diff --git a/db/schema_migrations/20221225010103 b/db/schema_migrations/20221225010103
new file mode 100644
index 0000000000000000000000000000000000000000..99590b1246f879fa421b34556fe171165817db8e
--- /dev/null
+++ b/db/schema_migrations/20221225010103
@@ -0,0 +1 @@
+bfa7df29a9f021b67db23127c6382161b131b77738f7a29dac5b64bc7431fd88
\ No newline at end of file
diff --git a/db/schema_migrations/20221225010104 b/db/schema_migrations/20221225010104
new file mode 100644
index 0000000000000000000000000000000000000000..abbf974cda0dfc5c909daa14f0627fe2a9d6421b
--- /dev/null
+++ b/db/schema_migrations/20221225010104
@@ -0,0 +1 @@
+b2b2a169bb1d8581eec2706d03314d0675dcdf05b23b2787292b18ac1dfe7847
\ No newline at end of file
diff --git a/db/schema_migrations/20221225010105 b/db/schema_migrations/20221225010105
new file mode 100644
index 0000000000000000000000000000000000000000..9f101f1aff31c054815e2f6468134265071e238b
--- /dev/null
+++ b/db/schema_migrations/20221225010105
@@ -0,0 +1 @@
+241ed02cdd479f06a5a4a817b2d27bfa970997167fbd67ddae1da8359830a2ea
\ No newline at end of file
diff --git a/db/schema_migrations/20221225010106 b/db/schema_migrations/20221225010106
new file mode 100644
index 0000000000000000000000000000000000000000..1499a3257eb5ff68ece1f4517d34ac6f1bdc1e39
--- /dev/null
+++ b/db/schema_migrations/20221225010106
@@ -0,0 +1 @@
+08e0fd85bca9eff63f0fc5d1e34cca628ee191decddebcb90aaf98ce18f97147
\ No newline at end of file
diff --git a/db/structure.sql b/db/structure.sql
index 695768fbfab1a0951a81781955d89d8e67f324c9..d47806f0c9bfe49ee421391d25799c3090acec6e 100644
--- a/db/structure.sql
+++ b/db/structure.sql
@@ -21783,6 +21783,25 @@ CREATE SEQUENCE releases_id_seq
ALTER SEQUENCE releases_id_seq OWNED BY releases.id;
+CREATE TABLE remote_development_agent_configs (
+ id bigint NOT NULL,
+ created_at timestamp with time zone NOT NULL,
+ updated_at timestamp with time zone NOT NULL,
+ cluster_agent_id bigint NOT NULL,
+ enabled boolean NOT NULL,
+ dns_zone text NOT NULL,
+ CONSTRAINT check_9f5cd54d1c CHECK ((char_length(dns_zone) <= 256))
+);
+
+CREATE SEQUENCE remote_development_agent_configs_id_seq
+ START WITH 1
+ INCREMENT BY 1
+ NO MINVALUE
+ NO MAXVALUE
+ CACHE 1;
+
+ALTER SEQUENCE remote_development_agent_configs_id_seq OWNED BY remote_development_agent_configs.id;
+
CREATE TABLE remote_mirrors (
id integer NOT NULL,
project_id integer,
@@ -24557,6 +24576,49 @@ CREATE SEQUENCE work_item_widget_definitions_id_seq
ALTER SEQUENCE work_item_widget_definitions_id_seq OWNED BY work_item_widget_definitions.id;
+CREATE TABLE workspaces (
+ id bigint NOT NULL,
+ created_at timestamp with time zone NOT NULL,
+ updated_at timestamp with time zone NOT NULL,
+ user_id bigint NOT NULL,
+ project_id bigint NOT NULL,
+ cluster_agent_id bigint NOT NULL,
+ desired_state_updated_at timestamp with time zone NOT NULL,
+ responded_to_agent_at timestamp with time zone,
+ max_hours_before_termination smallint NOT NULL,
+ name text NOT NULL,
+ namespace text NOT NULL,
+ desired_state text NOT NULL,
+ actual_state text NOT NULL,
+ editor text NOT NULL,
+ devfile_ref text NOT NULL,
+ devfile_path text NOT NULL,
+ devfile text,
+ processed_devfile text,
+ url text NOT NULL,
+ deployment_resource_version text,
+ CONSTRAINT check_15543fb0fa CHECK ((char_length(name) <= 64)),
+ CONSTRAINT check_157d5f955c CHECK ((char_length(namespace) <= 64)),
+ CONSTRAINT check_2b401b0034 CHECK ((char_length(deployment_resource_version) <= 64)),
+ CONSTRAINT check_77d1a2ff50 CHECK ((char_length(processed_devfile) <= 65535)),
+ CONSTRAINT check_8e363ee3ad CHECK ((char_length(devfile_ref) <= 256)),
+ CONSTRAINT check_8e4db5ffc2 CHECK ((char_length(actual_state) <= 32)),
+ CONSTRAINT check_9e42558c35 CHECK ((char_length(url) <= 1024)),
+ CONSTRAINT check_b70eddcbc1 CHECK ((char_length(desired_state) <= 32)),
+ CONSTRAINT check_d7ed376e49 CHECK ((char_length(editor) <= 256)),
+ CONSTRAINT check_dc58d56169 CHECK ((char_length(devfile_path) <= 2048)),
+ CONSTRAINT check_eb32879a3d CHECK ((char_length(devfile) <= 65535))
+);
+
+CREATE SEQUENCE workspaces_id_seq
+ START WITH 1
+ INCREMENT BY 1
+ NO MINVALUE
+ NO MAXVALUE
+ CACHE 1;
+
+ALTER SEQUENCE workspaces_id_seq OWNED BY workspaces.id;
+
CREATE TABLE x509_certificates (
id bigint NOT NULL,
created_at timestamp with time zone NOT NULL,
@@ -25560,6 +25622,8 @@ ALTER TABLE ONLY release_links ALTER COLUMN id SET DEFAULT nextval('release_link
ALTER TABLE ONLY releases ALTER COLUMN id SET DEFAULT nextval('releases_id_seq'::regclass);
+ALTER TABLE ONLY remote_development_agent_configs ALTER COLUMN id SET DEFAULT nextval('remote_development_agent_configs_id_seq'::regclass);
+
ALTER TABLE ONLY remote_mirrors ALTER COLUMN id SET DEFAULT nextval('remote_mirrors_id_seq'::regclass);
ALTER TABLE ONLY required_code_owners_sections ALTER COLUMN id SET DEFAULT nextval('required_code_owners_sections_id_seq'::regclass);
@@ -25800,6 +25864,8 @@ ALTER TABLE ONLY work_item_types ALTER COLUMN id SET DEFAULT nextval('work_item_
ALTER TABLE ONLY work_item_widget_definitions ALTER COLUMN id SET DEFAULT nextval('work_item_widget_definitions_id_seq'::regclass);
+ALTER TABLE ONLY workspaces ALTER COLUMN id SET DEFAULT nextval('workspaces_id_seq'::regclass);
+
ALTER TABLE ONLY x509_certificates ALTER COLUMN id SET DEFAULT nextval('x509_certificates_id_seq'::regclass);
ALTER TABLE ONLY x509_commit_signatures ALTER COLUMN id SET DEFAULT nextval('x509_commit_signatures_id_seq'::regclass);
@@ -27941,6 +28007,9 @@ ALTER TABLE releases
ALTER TABLE ONLY releases
ADD CONSTRAINT releases_pkey PRIMARY KEY (id);
+ALTER TABLE ONLY remote_development_agent_configs
+ ADD CONSTRAINT remote_development_agent_configs_pkey PRIMARY KEY (id);
+
ALTER TABLE ONLY remote_mirrors
ADD CONSTRAINT remote_mirrors_pkey PRIMARY KEY (id);
@@ -28352,6 +28421,9 @@ ALTER TABLE ONLY work_item_types
ALTER TABLE ONLY work_item_widget_definitions
ADD CONSTRAINT work_item_widget_definitions_pkey PRIMARY KEY (id);
+ALTER TABLE ONLY workspaces
+ ADD CONSTRAINT workspaces_pkey PRIMARY KEY (id);
+
ALTER TABLE ONLY x509_certificates
ADD CONSTRAINT x509_certificates_pkey PRIMARY KEY (id);
@@ -32167,6 +32239,8 @@ CREATE UNIQUE INDEX index_releases_on_project_tag_unique ON releases USING btree
CREATE INDEX index_releases_on_released_at ON releases USING btree (released_at);
+CREATE INDEX index_remote_development_agent_configs_on_cluster_agent_id ON remote_development_agent_configs USING btree (cluster_agent_id);
+
CREATE INDEX index_remote_mirrors_on_last_successful_update_at ON remote_mirrors USING btree (last_successful_update_at);
CREATE INDEX index_remote_mirrors_on_project_id ON remote_mirrors USING btree (project_id);
@@ -32975,6 +33049,14 @@ CREATE UNIQUE INDEX index_work_item_widget_definitions_on_namespace_type_and_nam
CREATE INDEX index_work_item_widget_definitions_on_work_item_type_id ON work_item_widget_definitions USING btree (work_item_type_id);
+CREATE INDEX index_workspaces_on_cluster_agent_id ON workspaces USING btree (cluster_agent_id);
+
+CREATE UNIQUE INDEX index_workspaces_on_name ON workspaces USING btree (name);
+
+CREATE INDEX index_workspaces_on_project_id ON workspaces USING btree (project_id);
+
+CREATE INDEX index_workspaces_on_user_id ON workspaces USING btree (user_id);
+
CREATE INDEX index_x509_certificates_on_subject_key_identifier ON x509_certificates USING btree (subject_key_identifier);
CREATE INDEX index_x509_certificates_on_x509_issuer_id ON x509_certificates USING btree (x509_issuer_id);
@@ -34601,6 +34683,9 @@ ALTER TABLE ONLY user_interacted_projects
ALTER TABLE ONLY merge_request_assignment_events
ADD CONSTRAINT fk_08f7602bfd FOREIGN KEY (merge_request_id) REFERENCES merge_requests(id) ON DELETE CASCADE;
+ALTER TABLE ONLY remote_development_agent_configs
+ ADD CONSTRAINT fk_0a3c0ada56 FOREIGN KEY (cluster_agent_id) REFERENCES cluster_agents(id) ON DELETE CASCADE;
+
ALTER TABLE ONLY dast_sites
ADD CONSTRAINT fk_0a57f2271b FOREIGN KEY (dast_site_validation_id) REFERENCES dast_site_validations(id) ON DELETE SET NULL;
@@ -35282,6 +35367,9 @@ ALTER TABLE ONLY resource_link_events
ALTER TABLE ONLY metrics_users_starred_dashboards
ADD CONSTRAINT fk_bd6ae32fac FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE;
+ALTER TABLE ONLY workspaces
+ ADD CONSTRAINT fk_bdb0b31131 FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE;
+
ALTER TABLE ONLY project_compliance_framework_settings
ADD CONSTRAINT fk_be413374a9 FOREIGN KEY (framework_id) REFERENCES compliance_management_frameworks(id) ON DELETE CASCADE;
@@ -35429,6 +35517,9 @@ ALTER TABLE ONLY web_hooks
ALTER TABLE ONLY security_scans
ADD CONSTRAINT fk_dbc89265b9 FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE;
+ALTER TABLE ONLY workspaces
+ ADD CONSTRAINT fk_dc7c316be1 FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE;
+
ALTER TABLE ONLY epics
ADD CONSTRAINT fk_dccd3f98fc FOREIGN KEY (assignee_id) REFERENCES users(id) ON DELETE SET NULL;
@@ -35531,6 +35622,9 @@ ALTER TABLE ONLY user_project_callouts
ALTER TABLE ONLY approval_merge_request_rules
ADD CONSTRAINT fk_f726c79756 FOREIGN KEY (scan_result_policy_id) REFERENCES scan_result_policies(id) ON DELETE CASCADE;
+ALTER TABLE ONLY workspaces
+ ADD CONSTRAINT fk_f78aeddc77 FOREIGN KEY (cluster_agent_id) REFERENCES cluster_agents(id) ON DELETE CASCADE;
+
ALTER TABLE ONLY cluster_agents
ADD CONSTRAINT fk_f7d43dee13 FOREIGN KEY (created_by_user_id) REFERENCES users(id) ON DELETE SET NULL;
diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md
index 8b8ac8a3d44601228011b2ddd6a49565d2344bb0..17243bf2f2cf36ddac8f3f7dc7d99e7e8a6cd5fa 100644
--- a/doc/api/graphql/reference/index.md
+++ b/doc/api/graphql/reference/index.md
@@ -760,6 +760,34 @@ Returns [`WorkItem`](#workitem).
| ---- | ---- | ----------- |
| `id` | [`WorkItemID!`](#workitemid) | Global ID of the work item. |
+### `Query.workspace`
+
+Find a workspace.
+
+Returns [`Workspace`](#workspace).
+
+#### Arguments
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| `id` | [`RemoteDevelopmentWorkspaceID!`](#remotedevelopmentworkspaceid) | Find a workspace by its ID. |
+
+### `Query.workspaces`
+
+Find workspaces by their IDs.
+
+Returns [`WorkspaceConnection`](#workspaceconnection).
+
+This field returns a [connection](#connections). It accepts the
+four standard [pagination arguments](#connection-pagination-arguments):
+`before: String`, `after: String`, `first: Int`, `last: Int`.
+
+#### Arguments
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| `ids` | [`[RemoteDevelopmentWorkspaceID!]`](#remotedevelopmentworkspaceid) | Array of global workspace IDs. For example, `["gid://gitlab/RemoteDevelopment::Workspace/1"]`. |
+
## `Mutation` type
The `Mutation` type contains all the mutations you can execute.
@@ -6835,6 +6863,51 @@ Input type: `WorkItemUpdateTaskInput`
| `task` | [`WorkItem`](#workitem) | Updated task. |
| `workItem` | [`WorkItem`](#workitem) | Updated work item. |
+### `Mutation.workspaceCreate`
+
+Input type: `WorkspaceCreateInput`
+
+#### Arguments
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| `clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
+| `clusterAgentId` | [`ClustersAgentID!`](#clustersagentid) | ID of the cluster agent the created workspace will be associated with. |
+| `desiredState` | [`String!`](#string) | Desired state of the created workspace. |
+| `devfilePath` | [`String!`](#string) | Project repo git path containing the devfile used to configure the workspace. |
+| `devfileRef` | [`String!`](#string) | Project repo git ref containing the devfile used to configure the workspace. |
+| `editor` | [`String!`](#string) | Editor to inject into the created workspace. Must match a configured template. |
+| `maxHoursBeforeTermination` | [`Int!`](#int) | Maximum hours the workspace can exist before it is automatically terminated. |
+| `projectId` | [`ProjectID!`](#projectid) | ID of the project that will provide the Devfile for the created workspace. |
+
+#### Fields
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| `clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
+| `errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. |
+| `workspace` | [`Workspace`](#workspace) | Created workspace. |
+
+### `Mutation.workspaceUpdate`
+
+Input type: `WorkspaceUpdateInput`
+
+#### Arguments
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| `clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
+| `desiredState` | [`String!`](#string) | Desired state of the created workspace. |
+| `id` | [`RemoteDevelopmentWorkspaceID!`](#remotedevelopmentworkspaceid) | Global ID of the workspace. |
+
+#### Fields
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| `clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
+| `errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. |
+| `workspace` | [`Workspace`](#workspace) | Created workspace. |
+
## Connections
Some types in our schema are `Connection` types - they represent a paginated
@@ -11109,6 +11182,29 @@ The edge type for [`WorkItemType`](#workitemtype).
| `cursor` | [`String!`](#string) | A cursor for use in pagination. |
| `node` | [`WorkItemType`](#workitemtype) | The item at the end of the edge. |
+#### `WorkspaceConnection`
+
+The connection type for [`Workspace`](#workspace).
+
+##### Fields
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| `edges` | [`[WorkspaceEdge]`](#workspaceedge) | A list of edges. |
+| `nodes` | [`[Workspace]`](#workspace) | A list of nodes. |
+| `pageInfo` | [`PageInfo!`](#pageinfo) | Information to aid in pagination. |
+
+#### `WorkspaceEdge`
+
+The edge type for [`Workspace`](#workspace).
+
+##### Fields
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| `cursor` | [`String!`](#string) | A cursor for use in pagination. |
+| `node` | [`Workspace`](#workspace) | The item at the end of the edge. |
+
## Object types
Object types represent the resources that the GitLab GraphQL API can return.
@@ -16745,6 +16841,22 @@ four standard [pagination arguments](#connection-pagination-arguments):
| `state` | [`[TodoStateEnum!]`](#todostateenum) | State of the todo. |
| `type` | [`[TodoTargetEnum!]`](#todotargetenum) | Type of the todo. |
+##### `MergeRequestAssignee.workspaces`
+
+Workspaces owned by the user.
+
+Returns [`WorkspaceConnection`](#workspaceconnection).
+
+This field returns a [connection](#connections). It accepts the
+four standard [pagination arguments](#connection-pagination-arguments):
+`before: String`, `after: String`, `first: Int`, `last: Int`.
+
+###### Arguments
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| `ids` | [`[RemoteDevelopmentWorkspaceID!]`](#remotedevelopmentworkspaceid) | Array of global workspace IDs. For example, `["gid://gitlab/RemoteDevelopment::Workspace/1"]`. |
+
### `MergeRequestAuthor`
The author of the merge request.
@@ -16995,6 +17107,22 @@ four standard [pagination arguments](#connection-pagination-arguments):
| `state` | [`[TodoStateEnum!]`](#todostateenum) | State of the todo. |
| `type` | [`[TodoTargetEnum!]`](#todotargetenum) | Type of the todo. |
+##### `MergeRequestAuthor.workspaces`
+
+Workspaces owned by the user.
+
+Returns [`WorkspaceConnection`](#workspaceconnection).
+
+This field returns a [connection](#connections). It accepts the
+four standard [pagination arguments](#connection-pagination-arguments):
+`before: String`, `after: String`, `first: Int`, `last: Int`.
+
+###### Arguments
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| `ids` | [`[RemoteDevelopmentWorkspaceID!]`](#remotedevelopmentworkspaceid) | Array of global workspace IDs. For example, `["gid://gitlab/RemoteDevelopment::Workspace/1"]`. |
+
### `MergeRequestDiffRegistry`
Represents the Geo sync and verification state of a Merge Request diff.
@@ -17264,6 +17392,22 @@ four standard [pagination arguments](#connection-pagination-arguments):
| `state` | [`[TodoStateEnum!]`](#todostateenum) | State of the todo. |
| `type` | [`[TodoTargetEnum!]`](#todotargetenum) | Type of the todo. |
+##### `MergeRequestParticipant.workspaces`
+
+Workspaces owned by the user.
+
+Returns [`WorkspaceConnection`](#workspaceconnection).
+
+This field returns a [connection](#connections). It accepts the
+four standard [pagination arguments](#connection-pagination-arguments):
+`before: String`, `after: String`, `first: Int`, `last: Int`.
+
+###### Arguments
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| `ids` | [`[RemoteDevelopmentWorkspaceID!]`](#remotedevelopmentworkspaceid) | Array of global workspace IDs. For example, `["gid://gitlab/RemoteDevelopment::Workspace/1"]`. |
+
### `MergeRequestPermissions`
Check permissions for the current user on a merge request.
@@ -17533,6 +17677,22 @@ four standard [pagination arguments](#connection-pagination-arguments):
| `state` | [`[TodoStateEnum!]`](#todostateenum) | State of the todo. |
| `type` | [`[TodoTargetEnum!]`](#todotargetenum) | Type of the todo. |
+##### `MergeRequestReviewer.workspaces`
+
+Workspaces owned by the user.
+
+Returns [`WorkspaceConnection`](#workspaceconnection).
+
+This field returns a [connection](#connections). It accepts the
+four standard [pagination arguments](#connection-pagination-arguments):
+`before: String`, `after: String`, `first: Int`, `last: Int`.
+
+###### Arguments
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| `ids` | [`[RemoteDevelopmentWorkspaceID!]`](#remotedevelopmentworkspaceid) | Array of global workspace IDs. For example, `["gid://gitlab/RemoteDevelopment::Workspace/1"]`. |
+
### `Metadata`
#### Fields
@@ -21948,6 +22108,22 @@ four standard [pagination arguments](#connection-pagination-arguments):
| `state` | [`[TodoStateEnum!]`](#todostateenum) | State of the todo. |
| `type` | [`[TodoTargetEnum!]`](#todotargetenum) | Type of the todo. |
+##### `UserCore.workspaces`
+
+Workspaces owned by the user.
+
+Returns [`WorkspaceConnection`](#workspaceconnection).
+
+This field returns a [connection](#connections). It accepts the
+four standard [pagination arguments](#connection-pagination-arguments):
+`before: String`, `after: String`, `first: Int`, `last: Int`.
+
+###### Arguments
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| `ids` | [`[RemoteDevelopmentWorkspaceID!]`](#remotedevelopmentworkspaceid) | Array of global workspace IDs. For example, `["gid://gitlab/RemoteDevelopment::Workspace/1"]`. |
+
### `UserMergeRequestInteraction`
Information about a merge request given a specific user.
@@ -22932,6 +23108,35 @@ Represents a weight widget.
| `type` | [`WorkItemWidgetType`](#workitemwidgettype) | Widget type. |
| `weight` | [`Int`](#int) | Weight of the work item. |
+### `Workspace`
+
+Represents a remote development workspace.
+
+#### Fields
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| `actualState` | [`String!`](#string) | Actual state of the workspace. |
+| `clusterAgent` | [`ClusterAgent!`](#clusteragent) | Kubernetes Agent associated with the workspace. |
+| `createdAt` | [`Time!`](#time) | Timestamp of workspace creation. |
+| `deploymentResourceVersion` | [`Int`](#int) | ResourceVersion of the Deployment resource for the workspace. |
+| `desiredState` | [`String!`](#string) | Desired state of the workspace. |
+| `desiredStateUpdatedAt` | [`Time!`](#time) | Timestamp of last update to desired state. |
+| `devfile` | [`String!`](#string) | Source YAML of the devfile used to configure the workspace. |
+| `devfilePath` | [`String!`](#string) | Project repo git path containing the devfile used to configure the workspace. |
+| `devfileRef` | [`String!`](#string) | Project repo git ref containing the devfile used to configure the workspace. |
+| `editor` | [`String!`](#string) | Editor used to configure the workspace. Must match a configured template. |
+| `id` | [`RemoteDevelopmentWorkspaceID!`](#remotedevelopmentworkspaceid) | Global ID of the workspace. |
+| `maxHoursBeforeTermination` | [`Int!`](#int) | Maximum hours the workspace can exist before it is automatically terminated. |
+| `name` | [`String!`](#string) | Name of the workspace in Kubernetes. |
+| `namespace` | [`String!`](#string) | Namespace of the workspace in Kubernetes. |
+| `processedDevfile` | [`String!`](#string) | Processed YAML of the devfile used to configure the workspace. |
+| `projectId` | [`ID!`](#id) | ID of the Project providing the Devfile for the workspace. |
+| `respondedToAgentAt` | [`Time`](#time) | Timestamp of last response sent to GA4K for the workspace. |
+| `updatedAt` | [`Time!`](#time) | Timestamp of last update to any mutable workspace property. |
+| `url` | [`String!`](#string) | URL of the workspace. |
+| `user` | [`UserCore!`](#usercore) | Owner of the workspace. |
+
### `X509Certificate`
Represents an X.509 certificate.
@@ -25857,6 +26062,12 @@ A `ReleasesLinkID` is a global ID. It is encoded as a string.
An example `ReleasesLinkID` is: `"gid://gitlab/Releases::Link/1"`.
+### `RemoteDevelopmentWorkspaceID`
+
+A `RemoteDevelopmentWorkspaceID` is a global ID. It is encoded as a string.
+
+An example `RemoteDevelopmentWorkspaceID` is: `"gid://gitlab/RemoteDevelopment::Workspace/1"`.
+
### `SecurityTrainingProviderID`
A `SecurityTrainingProviderID` is a global ID. It is encoded as a string.
@@ -26637,6 +26848,22 @@ four standard [pagination arguments](#connection-pagination-arguments):
| `state` | [`[TodoStateEnum!]`](#todostateenum) | State of the todo. |
| `type` | [`[TodoTargetEnum!]`](#todotargetenum) | Type of the todo. |
+###### `User.workspaces`
+
+Workspaces owned by the user.
+
+Returns [`WorkspaceConnection`](#workspaceconnection).
+
+This field returns a [connection](#connections). It accepts the
+four standard [pagination arguments](#connection-pagination-arguments):
+`before: String`, `after: String`, `first: Int`, `last: Int`.
+
+####### Arguments
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| `ids` | [`[RemoteDevelopmentWorkspaceID!]`](#remotedevelopmentworkspaceid) | Array of global workspace IDs. For example, `["gid://gitlab/RemoteDevelopment::Workspace/1"]`. |
+
#### `WorkItemWidget`
Implementations:
diff --git a/ee/app/assets/javascripts/remote_development/components/create/get_project_details_query.vue b/ee/app/assets/javascripts/remote_development/components/create/get_project_details_query.vue
index 5c6725273cd6f3f3baa8a5bbe116cc28ac10690b..f14f20903c01550e5f5793412181046fe9998ddb 100644
--- a/ee/app/assets/javascripts/remote_development/components/create/get_project_details_query.vue
+++ b/ee/app/assets/javascripts/remote_development/components/create/get_project_details_query.vue
@@ -71,6 +71,7 @@ export default {
}
try {
+ // noinspection JSCheckFunctionSignatures
const { data, error } = await this.$apollo.query({
query: getGroupClusterAgentsQuery,
variables: { groupPath },
diff --git a/ee/app/assets/javascripts/remote_development/components/list/start_workspace_button.vue b/ee/app/assets/javascripts/remote_development/components/list/start_workspace_button.vue
index f25f1c30542d86e3f243f52bb6a2154133cdeb6a..95989cad515b7bb8f03f19a3acbf161cc2f134c1 100644
--- a/ee/app/assets/javascripts/remote_development/components/list/start_workspace_button.vue
+++ b/ee/app/assets/javascripts/remote_development/components/list/start_workspace_button.vue
@@ -28,7 +28,11 @@ export default {
computed: {
isVisible() {
return (
- [WORKSPACE_STATES.stopped, WORKSPACE_STATES.starting].includes(this.actualState) &&
+ [
+ WORKSPACE_STATES.creationRequested,
+ WORKSPACE_STATES.stopped,
+ WORKSPACE_STATES.starting,
+ ].includes(this.actualState) &&
[WORKSPACE_DESIRED_STATES.running, WORKSPACE_DESIRED_STATES.stopped].includes(
this.desiredState,
)
diff --git a/ee/app/assets/javascripts/remote_development/components/list/workspace_state_indicator.vue b/ee/app/assets/javascripts/remote_development/components/list/workspace_state_indicator.vue
index 666db17191ed2977cda56d369094777afc1ad63d..07fc19502166272afd2c03d64be3a3fcde7fc17d 100644
--- a/ee/app/assets/javascripts/remote_development/components/list/workspace_state_indicator.vue
+++ b/ee/app/assets/javascripts/remote_development/components/list/workspace_state_indicator.vue
@@ -1,7 +1,12 @@