Skip to content
代码片段 群组 项目
未验证 提交 cfe04100 编辑于 作者: Doug Stull's avatar Doug Stull 提交者: GitLab
浏览文件

Merge branch 'caw-ws-add-actual-state-workspace-var-1-5' into 'master'

Refactor desired_config_generator.rb to Builder pattern

See merge request https://gitlab.com/gitlab-org/gitlab/-/merge_requests/183632



Merged-by: default avatarDoug Stull <dstull@gitlab.com>
Approved-by: default avatarDaniyal Arshad <darshad@gitlab.com>
Approved-by: default avatarDoug Stull <dstull@gitlab.com>
Reviewed-by: default avatarChad Woolley <cwoolley@gitlab.com>
Reviewed-by: default avatarDaniyal Arshad <darshad@gitlab.com>
Co-authored-by: default avatarChad Woolley <cwoolley@gitlab.com>
No related branches found
No related tags found
2 合并请求!3031Merge per-main-jh to main-jh by luzhiyuan,!3030Merge per-main-jh to main-jh
......@@ -369,6 +369,43 @@ end
You can choose which is more appropriate for your use case.
#### Usage of variables in pattern matching
You can also use variables in pattern matching. In the following example, `id_to_find` is used in
the pattern match via
[the `^` pattern operator](https://docs.ruby-lang.org/en/master/syntax/pattern_matching_rdoc.html#label-Appendix+A.+Pattern+syntax)
```ruby
id_to_find = 2
hash = [{ id: 1, value: "a" }, { id: 2, value: "b" }, { id: 3, value: "c" }]
hash => [
*_,
{
id: ^id_to_find,
value: value
},
*_
]
puts value # => b
```
You can also use expressions via the `^(....)` pattern operator:
```ruby
major_release = 1
minor_release = 2
hash = [{ id: "1.1", value: "a" }, { id: "1.2", value: "b" }, { id: "1.3", value: "c" }]
hash => [
*_,
{
id: ^("#{major_release}.#{minor_release}"),
value: value
},
*_
]
puts value # => b
```
#### Do not use type safety on unvalidated user-provided values
We do not want to use type safety on values which come directly from user input, and have not yet been validated.
......
......@@ -7,63 +7,46 @@ module Output
class DesiredConfigGenerator
include States
# @param [RemoteDevelopment::WorkspaceOperations::Workspace] workspace
# @param [RemoteDevelopment::Workspace] workspace
# @param [Boolean] include_all_resources
# @param [RemoteDevelopment::Logger] logger
# @return [Array<Hash>]
def self.generate_desired_config(workspace:, include_all_resources:, logger:)
desired_config = []
workspaces_agent_config = workspace.workspaces_agent_config
# NOTE: update env_secret_name to "#{workspace.name}-environment". This is to ensure naming consistency.
# Changing it now would require migration from old config version to a new one.
# Update this when a new desired config generator is created for some other reason.
env_secret_name = "#{workspace.name}-env-var"
file_secret_name = "#{workspace.name}-file"
domain_template = get_domain_template_annotation(
name: workspace.name,
dns_zone: workspaces_agent_config.dns_zone
)
inventory_name = "#{workspace.name}-workspace-inventory"
workspaces_agent_config = workspace.workspaces_agent_config
max_resources_per_workspace = workspaces_agent_config.max_resources_per_workspace.deep_symbolize_keys
labels, annotations = get_merged_labels_and_annotations(
agent_labels: workspaces_agent_config.labels,
agent_annotations: workspaces_agent_config.annotations,
agent_id: workspace.agent.id,
agent_annotations = workspaces_agent_config.annotations
domain_template = "{{.port}}-#{workspace.name}.#{workspaces_agent_config.dns_zone}"
common_annotations = get_common_annotations(
agent_annotations: agent_annotations,
domain_template: domain_template,
owning_inventory: inventory_name,
workspace_id: workspace.id,
max_resources_per_workspace: workspaces_agent_config.max_resources_per_workspace.deep_symbolize_keys
max_resources_per_workspace: max_resources_per_workspace
)
k8s_inventory_for_workspace_core = get_inventory_config_map(
name: inventory_name,
namespace: workspace.namespace,
agent_labels: workspaces_agent_config.labels,
agent_annotations: workspaces_agent_config.annotations,
agent_id: workspace.agent.id
)
workspace_inventory_name = "#{workspace.name}-workspace-inventory"
workspace_inventory_annotations =
common_annotations.merge("config.k8s.io/owning-inventory": workspace_inventory_name)
k8s_resources_params = {
name: workspace.name,
namespace: workspace.namespace,
replicas: get_workspace_replicas(desired_state: workspace.desired_state),
domain_template: domain_template,
labels: labels,
annotations: annotations,
env_secret_names: [env_secret_name],
file_secret_names: [file_secret_name],
service_account_name: workspace.name,
default_resources_per_workspace_container: workspaces_agent_config
.default_resources_per_workspace_container
.deep_symbolize_keys,
allow_privilege_escalation: workspaces_agent_config.allow_privilege_escalation,
use_kubernetes_user_namespaces: workspaces_agent_config.use_kubernetes_user_namespaces,
default_runtime_class: workspaces_agent_config.default_runtime_class
}
labels = workspaces_agent_config.labels.merge({ "agent.gitlab.com/id": workspace.agent.id.to_s })
k8s_resources_for_workspace_core = DevfileParser.get_all(
resources_from_devfile_parser = DevfileParser.get_all(
processed_devfile: workspace.processed_devfile,
k8s_resources_params: k8s_resources_params,
params: get_devfile_parser_params(
workspace: workspace,
workspaces_agent_config: workspaces_agent_config,
domain_template: domain_template,
labels: labels,
annotations: workspace_inventory_annotations,
env_secret_name: env_secret_name,
file_secret_name: file_secret_name
),
logger: logger
)
......@@ -71,151 +54,130 @@ def self.generate_desired_config(workspace:, include_all_resources:, logger:)
# the processed_devfile. So we return an empty array which will result in no updates being applied by the
# agent. We should not continue on and try to add anything else to the resources, as this would result
# in an invalid configuration being applied to the cluster.
return [] if k8s_resources_for_workspace_core.empty?
return [] if resources_from_devfile_parser.empty?
desired_config.append(k8s_inventory_for_workspace_core, *k8s_resources_for_workspace_core)
desired_config = []
workspace_service_account_definition = get_image_pull_secrets_service_account(
name: workspace.name,
append_inventory_config_map(
desired_config: desired_config,
name: workspace_inventory_name,
namespace: workspace.namespace,
image_pull_secrets: workspaces_agent_config.image_pull_secrets,
labels: labels,
annotations: annotations
agent_annotations: agent_annotations
)
desired_config.append(workspace_service_account_definition)
if workspaces_agent_config.network_policy_enabled
network_policy = get_network_policy(
name: workspace.name,
namespace: workspace.namespace,
labels: labels,
annotations: annotations,
gitlab_workspaces_proxy_namespace: workspaces_agent_config.gitlab_workspaces_proxy_namespace,
egress_ip_rules: workspaces_agent_config.network_policy_egress
)
desired_config.append(network_policy)
end
return desired_config unless include_all_resources
desired_config.append(*resources_from_devfile_parser)
desired_config + get_extra_k8s_resources(
workspace: workspace,
append_image_pull_secrets_service_account(
desired_config: desired_config,
name: workspace.name,
namespace: workspace.namespace,
image_pull_secrets: workspaces_agent_config.image_pull_secrets,
labels: labels,
annotations: annotations,
env_secret_name: env_secret_name,
file_secret_name: file_secret_name
annotations: workspace_inventory_annotations
)
end
# @param [RemoteDevelopment::WorkspaceOperations::Workspace] workspace
# @param [Hash<String, String>] labels
# @param [Hash<String, String>] annotations
# @param [String] env_secret_name
# @param [String] file_secret_name
# @param [Hash] desired_config
# @return [Array<(Hash)>]
def self.get_extra_k8s_resources(
workspace:,
labels:,
annotations:,
env_secret_name:,
file_secret_name:
)
workspaces_agent_config = workspace.workspaces_agent_config
agent_annotations = workspaces_agent_config.annotations
agent_labels = workspaces_agent_config.labels
max_resources_per_workspace = workspaces_agent_config.max_resources_per_workspace.deep_symbolize_keys
extra_config = []
append_network_policy(
desired_config: desired_config,
workspaces_agent_config: workspaces_agent_config,
name: workspace.name,
namespace: workspace.namespace,
labels: labels,
annotations: workspace_inventory_annotations
)
unless max_resources_per_workspace.blank?
k8s_resource_quota = get_resource_quota(
name: workspace.name,
namespace: workspace.namespace,
labels: labels,
annotations: annotations,
max_resources_per_workspace: max_resources_per_workspace
)
extra_config.append(k8s_resource_quota)
end
secrets_inventory_name = "#{workspace.name}-secrets-inventory"
secrets_inventory_annotations =
common_annotations.merge("config.k8s.io/owning-inventory": secrets_inventory_name)
k8s_resources_for_secrets = get_k8s_resources_for_secrets(
workspace: workspace,
agent_labels: agent_labels,
append_inventory_config_map(
desired_config: desired_config,
name: secrets_inventory_name,
labels: labels,
agent_annotations: agent_annotations,
env_secret_name: env_secret_name,
file_secret_name: file_secret_name,
max_resources_per_workspace: max_resources_per_workspace
namespace: workspace.namespace
)
extra_config.append(*k8s_resources_for_secrets)
extra_config
end
# NOTE: We will perform append_secret here in order to complete
# https://gitlab.com/gitlab-org/gitlab/-/merge_requests/182392
# @param [RemoteDevelopment::WorkspaceOperations::Workspace] workspace
# @param [Hash<String, String>] agent_labels
# @param [Hash<String, String>] agent_annotations
# @param [String] env_secret_name
# @param [String] file_secret_name
# @param [String] env_secret_name
# @param [String] file_secret_name
# @param [Hash] max_resources_per_workspace
# @return [Array<(Hash)>]
def self.get_k8s_resources_for_secrets(
workspace:,
agent_labels:,
agent_annotations:,
env_secret_name:,
file_secret_name:,
max_resources_per_workspace:
)
inventory_name = "#{workspace.name}-secrets-inventory"
domain_template = get_domain_template_annotation(
name: workspace.name,
dns_zone: workspace.workspaces_agent_config.dns_zone
)
labels, annotations = get_merged_labels_and_annotations(
agent_labels: agent_labels,
agent_annotations: agent_annotations,
agent_id: workspace.agent.id,
domain_template: domain_template,
owning_inventory: inventory_name,
workspace_id: workspace.id,
max_resources_per_workspace: max_resources_per_workspace
)
return desired_config unless include_all_resources
k8s_inventory = get_inventory_config_map(
name: inventory_name,
agent_labels: agent_labels,
agent_annotations: agent_annotations,
append_resource_quota(
desired_config: desired_config,
name: workspace.name,
namespace: workspace.namespace,
agent_id: workspace.agent.id
labels: labels,
annotations: workspace_inventory_annotations,
max_resources_per_workspace: max_resources_per_workspace
)
data_for_environment = workspace.workspace_variables.with_variable_type_environment
data_for_environment = data_for_environment.each_with_object({}) do |workspace_variable, hash|
hash[workspace_variable.key.to_sym] = workspace_variable.value
end
k8s_secret_for_environment = get_secret(
append_secret(
desired_config: desired_config,
name: env_secret_name,
namespace: workspace.namespace,
labels: labels,
annotations: annotations,
data: data_for_environment
annotations: secrets_inventory_annotations
)
data_for_file = workspace.workspace_variables.with_variable_type_file
data_for_file = data_for_file.each_with_object({}) do |workspace_variable, hash|
hash[workspace_variable.key.to_sym] = workspace_variable.value
end
k8s_secret_for_file = get_secret(
append_secret_data_from_variables(
desired_config: desired_config,
secret_name: env_secret_name,
variables: workspace.workspace_variables.with_variable_type_environment
)
append_secret(
desired_config: desired_config,
name: file_secret_name,
namespace: workspace.namespace,
labels: labels,
annotations: annotations,
data: data_for_file
annotations: secrets_inventory_annotations
)
append_secret_data_from_variables(
desired_config: desired_config,
secret_name: file_secret_name,
variables: workspace.workspace_variables.with_variable_type_file
)
[k8s_inventory, k8s_secret_for_environment, k8s_secret_for_file]
desired_config
end
# @param [RemoteDevelopment::Workspace] workspace
# @param [RemoteDevelopment::WorkspacesAgentConfig] workspaces_agent_config
# @param [String] domain_template
# @param [Hash<String, String>] labels
# @param [Hash<String, String>] annotations
# @param [String] env_secret_name
# @param [String] file_secret_name
# @return [Hash]
def self.get_devfile_parser_params(
workspace:,
workspaces_agent_config:,
domain_template:,
labels:,
annotations:,
env_secret_name:,
file_secret_name:
)
{
name: workspace.name,
namespace: workspace.namespace,
replicas: get_workspace_replicas(desired_state: workspace.desired_state),
domain_template: domain_template,
labels: labels,
annotations: annotations,
env_secret_names: [env_secret_name],
file_secret_names: [file_secret_name],
service_account_name: workspace.name,
default_resources_per_workspace_container:
workspaces_agent_config
.default_resources_per_workspace_container
.deep_symbolize_keys,
allow_privilege_escalation: workspaces_agent_config.allow_privilege_escalation,
use_kubernetes_user_namespaces: workspaces_agent_config.use_kubernetes_user_namespaces,
default_runtime_class: workspaces_agent_config.default_runtime_class
}
end
# @param [String] desired_state
......@@ -229,108 +191,147 @@ def self.get_workspace_replicas(desired_state:)
0
end
# @param [Array] desired_config
# @param [String] name
# @param [String] namespace
# @param [Hash<String, String>] agent_labels
# @param [Hash<String, String>] labels
# @param [Hash<String, String>] agent_annotations
# @param [Integer] agent_id
# @return [Hash]
def self.get_inventory_config_map(name:, namespace:, agent_labels:, agent_annotations:, agent_id:)
extra_labels = {
'cli-utils.sigs.k8s.io/inventory-id': name,
'agent.gitlab.com/id': agent_id.to_s
}
labels = agent_labels.merge(extra_labels)
{
kind: 'ConfigMap',
apiVersion: 'v1',
# @return [void]
def self.append_inventory_config_map(
desired_config:,
name:,
namespace:,
labels:,
agent_annotations:
)
extra_labels = { "cli-utils.sigs.k8s.io/inventory-id": name }
config_map = {
kind: "ConfigMap",
apiVersion: "v1",
metadata: {
name: name,
namespace: namespace,
labels: labels,
labels: labels.merge(extra_labels),
annotations: agent_annotations
}
}
desired_config.append(config_map)
nil
end
# @param [Hash<String, String>] agent_labels
# @param [Hash<String, String>] agent_annotations
# @param [Integer] agent_id
# @param [String] domain_template
# @param [String] owning_inventory
# @param [String] object_type
# @param [Integer] workspace_id
# @param [Hash] max_resources_per_workspace
# @return [Array<Hash, Hash>]
def self.get_merged_labels_and_annotations(
agent_labels:,
# @return [Hash]
def self.get_common_annotations(
agent_annotations:,
agent_id:,
domain_template:,
owning_inventory:,
workspace_id:,
max_resources_per_workspace:
)
extra_labels = {
'agent.gitlab.com/id': agent_id.to_s
}
labels = agent_labels.merge(extra_labels)
extra_annotations = {
'config.k8s.io/owning-inventory': owning_inventory.to_s,
'workspaces.gitlab.com/host-template': domain_template.to_s,
'workspaces.gitlab.com/id': workspace_id.to_s,
'workspaces.gitlab.com/max-resources-per-workspace-sha256':
"workspaces.gitlab.com/host-template": domain_template.to_s,
"workspaces.gitlab.com/id": workspace_id.to_s,
# NOTE: This annotation is added to cause the workspace to restart whenever the max resources change
"workspaces.gitlab.com/max-resources-per-workspace-sha256":
OpenSSL::Digest::SHA256.hexdigest(max_resources_per_workspace.sort.to_h.to_s)
}
annotations = agent_annotations.merge(extra_annotations)
[labels, annotations]
agent_annotations.merge(extra_annotations)
end
# @param [Array] desired_config
# @param [String] name
# @param [String] namespace
# @param [Hash] labels
# @param [Hash] annotations
# @param [Hash] data
# @return [Hash]
def self.get_secret(name:, namespace:, labels:, annotations:, data:)
{
kind: 'Secret',
apiVersion: 'v1',
# @return [void]
def self.append_secret(desired_config:, name:, namespace:, labels:, annotations:)
secret = {
kind: "Secret",
apiVersion: "v1",
metadata: {
name: name,
namespace: namespace,
labels: labels,
annotations: annotations
},
data: data.transform_values { |v| Base64.strict_encode64(v) }
data: {}
}
desired_config.append(secret)
nil
end
# @param [String] name
# @param [String] dns_zone
# @return [String]
def self.get_domain_template_annotation(name:, dns_zone:)
"{{.port}}-#{name}.#{dns_zone}"
# @param [Array] desired_config
# @param [String] secret_name
# @param [ActiveRecord::Relation<RemoteDevelopment::WorkspaceVariable>] variables
# @return [void]
def self.append_secret_data_from_variables(desired_config:, secret_name:, variables:)
data = variables.each_with_object({}) do |workspace_variable, hash|
hash[workspace_variable.key.to_sym] = workspace_variable.value
end
append_secret_data(
desired_config: desired_config,
secret_name: secret_name,
data: data
)
nil
end
# @param [Array] desired_config
# @param [String] secret_name
# @param [Hash] data
# @return [void]
# noinspection RubyUnusedLocalVariable -- Rubymine doesn't recognize '^' to use a variable in pattern-matching
def self.append_secret_data(desired_config:, secret_name:, data:)
desired_config => [
*_,
{
metadata: {
name: ^secret_name
},
data: secret_data
},
*_
]
transformed_data = data.transform_values { |value| Base64.strict_encode64(value) }
secret_data.merge!(transformed_data)
nil
end
# @param [Array] desired_config
# @param [RemoteDevelopment::WorkspacesAgentConfig] workspaces_agent_config
# @param [String] name
# @param [String] namespace
# @param [Hash] labels
# @param [Hash] annotations
# @param [string] gitlab_workspaces_proxy_namespace
# @param [Array<Hash>] egress_ip_rules
# @return [Hash]
def self.get_network_policy(
# @return [void]
def self.append_network_policy(
desired_config:,
workspaces_agent_config:,
name:,
namespace:,
labels:,
annotations:,
gitlab_workspaces_proxy_namespace:,
egress_ip_rules:
annotations:
)
return unless workspaces_agent_config.network_policy_enabled
gitlab_workspaces_proxy_namespace = workspaces_agent_config.gitlab_workspaces_proxy_namespace
egress_ip_rules = workspaces_agent_config.network_policy_egress
policy_types = [
- "Ingress",
- "Egress"
-"Ingress",
-"Egress"
]
proxy_namespace_selector = {
......@@ -363,7 +364,7 @@ def self.get_network_policy(
)
end
{
network_policy = {
apiVersion: "networking.k8s.io/v1",
kind: "NetworkPolicy",
metadata: {
......@@ -379,21 +380,29 @@ def self.get_network_policy(
policyTypes: policy_types
}
}
desired_config.append(network_policy)
nil
end
# @param [Array] desired_config
# @param [String] name
# @param [String] namespace
# @param [Hash] labels
# @param [Hash] annotations
# @param [Hash] max_resources_per_workspace
# @return [Hash]
def self.get_resource_quota(
# @return [void]
def self.append_resource_quota(
desired_config:,
name:,
namespace:,
labels:,
annotations:,
max_resources_per_workspace:
)
return unless max_resources_per_workspace.present?
max_resources_per_workspace => {
limits: {
cpu: limits_cpu,
......@@ -405,7 +414,7 @@ def self.get_resource_quota(
}
}
{
resource_quota = {
apiVersion: "v1",
kind: "ResourceQuota",
metadata: {
......@@ -423,19 +432,32 @@ def self.get_resource_quota(
}
}
}
desired_config.append(resource_quota)
nil
end
# @param [Array] desired_config
# @param [String] name
# @param [String] namespace
# @param [Hash] labels
# @param [Hash] annotations
# @param [Array] image_pull_secrets
# @return [Hash]
def self.get_image_pull_secrets_service_account(name:, namespace:, labels:, annotations:, image_pull_secrets:)
image_pull_secrets_names = image_pull_secrets.map { |secret| { name: secret.fetch('name') } }
{
apiVersion: 'v1',
kind: 'ServiceAccount',
# @return [void]
def self.append_image_pull_secrets_service_account(
desired_config:,
name:,
namespace:,
labels:,
annotations:,
image_pull_secrets:
)
image_pull_secrets_names = image_pull_secrets.map { |secret| { name: secret.fetch("name") } }
workspace_service_account_definition = {
apiVersion: "v1",
kind: "ServiceAccount",
metadata: {
name: name,
namespace: namespace,
......@@ -445,7 +467,15 @@ def self.get_image_pull_secrets_service_account(name:, namespace:, labels:, anno
automountServiceAccountToken: false,
imagePullSecrets: image_pull_secrets_names
}
desired_config.append(workspace_service_account_definition)
nil
end
private_class_method :get_devfile_parser_params, :get_workspace_replicas, :append_inventory_config_map,
:get_common_annotations, :append_secret, :append_secret_data_from_variables, :append_secret_data,
:append_network_policy, :append_resource_quota, :append_image_pull_secrets_service_account
end
end
end
......
......@@ -11,11 +11,11 @@ class DevfileParser
include ReconcileConstants
# @param [String] processed_devfile
# @param [Hash] k8s_resources_params
# @param [Hash] params
# @param [RemoteDevelopment::Logger] logger
# @return [Array<Hash>]
def self.get_all(processed_devfile:, k8s_resources_params:, logger:)
k8s_resources_params => {
def self.get_all(processed_devfile:, params:, logger:)
params => {
name: String => name,
namespace: String => namespace,
replicas: Integer => replicas,
......
......@@ -79,7 +79,7 @@
let(:labels) { {} }
let(:annotations) { {} }
let(:k8s_resources_params) do
let(:params) do
{
name: workspace.name,
namespace: workspace.namespace,
......@@ -119,10 +119,10 @@
)
end
subject(:k8s_resources_for_workspace_core) do
subject(:resources_from_devfile_parser) do
described_class.get_all(
processed_devfile: processed_devfile_yaml,
k8s_resources_params: k8s_resources_params,
params: params,
logger: logger
)
end
......@@ -145,9 +145,9 @@
it 'returns workspace_resources with allow_privilege_escalation set to true',
:unlimited_max_formatted_output_length do
expect(k8s_resources_for_workspace_core).to eq(expected_workspace_resources)
expect(resources_from_devfile_parser).to eq(expected_workspace_resources)
k8s_resources_for_workspace_core => [
resources_from_devfile_parser => [
*_,
{
kind: "Deployment",
......@@ -181,9 +181,9 @@
let(:use_kubernetes_user_namespaces) { true }
it 'returns workspace_resources with hostUsers set to true' do
expect(k8s_resources_for_workspace_core).to eq(expected_workspace_resources)
expect(resources_from_devfile_parser).to eq(expected_workspace_resources)
k8s_resources_for_workspace_core => [
resources_from_devfile_parser => [
*_,
{
kind: "Deployment",
......@@ -217,7 +217,7 @@
workspace_namespace: workspace.namespace,
devfile_parser_error: "some error"
)
expect(k8s_resources_for_workspace_core).to eq([])
expect(resources_from_devfile_parser).to eq([])
end
end
......
......@@ -488,10 +488,10 @@ def create_config_to_apply_v3(
unless core_resources_only
resources << workspace_service_account
resources << workspace_network_policy if include_network_policy
resources << workspace_secrets_inventory if include_inventory
if include_all_resources
resources << workspace_resource_quota unless max_resources_per_workspace.blank?
resources << workspace_secrets_inventory if include_inventory
resources << workspace_secret_environment
resources << workspace_secret_file
end
......
0% 加载中 .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册