diff --git a/lib/aws/bootstrap/tf-providers-aws.j2.tf b/lib/aws/bootstrap/tf-providers-aws.j2.tf index 46cdc56d..18093334 100644 --- a/lib/aws/bootstrap/tf-providers-aws.j2.tf +++ b/lib/aws/bootstrap/tf-providers-aws.j2.tf @@ -10,7 +10,7 @@ terraform { } vault = { source = "hashicorp/vault" - version = "~> 2.18.0" + version = "~> 2.24.1" } local = { source = "hashicorp/local" diff --git a/lib/digitalocean/bootstrap/tf-providers.j2.tf b/lib/digitalocean/bootstrap/tf-providers.j2.tf index aaadc47a..bd5bf507 100644 --- a/lib/digitalocean/bootstrap/tf-providers.j2.tf +++ b/lib/digitalocean/bootstrap/tf-providers.j2.tf @@ -43,7 +43,7 @@ terraform { } vault = { source = "hashicorp/vault" - version = "~> 2.18.0" + version = "~> 2.24.1" } } required_version = ">= 0.14" diff --git a/lib/digitalocean/charts/q-ingress-tls/templates/ingress-qovery.j2.yaml b/lib/digitalocean/charts/q-ingress-tls/templates/ingress-qovery.j2.yaml index a5ca0808..d0bf7559 100644 --- a/lib/digitalocean/charts/q-ingress-tls/templates/ingress-qovery.j2.yaml +++ b/lib/digitalocean/charts/q-ingress-tls/templates/ingress-qovery.j2.yaml @@ -14,7 +14,6 @@ metadata: annotations: external-dns.alpha.kubernetes.io/hostname: {{ router_default_domain }} external-dns.alpha.kubernetes.io/ttl: "300" - external-dns.alpha.kubernetes.io/target: "{{ external_ingress_hostname_default }}" kubernetes.io/tls-acme: "true" {%- if custom_domains|length > 0 %} cert-manager.io/issuer: {{ id }} diff --git a/lib/scaleway/services/mysql/main.j2.tf b/lib/scaleway/services/mysql/main.j2.tf index 3ed8b768..1bd21885 100644 --- a/lib/scaleway/services/mysql/main.j2.tf +++ b/lib/scaleway/services/mysql/main.j2.tf @@ -13,25 +13,21 @@ locals { tags_mysql_list = [for i, v in local.tags_mysql : "${i}=${v}"] # NOTE: Scaleway doesn't support KV style tags } +{%- if publicly_accessible != false %} +# DB - ACL +# The initial setup of an instance allows full network access from anywhere (0.0.0.0/0). resource "scaleway_rdb_acl" "main" { instance_id = scaleway_rdb_instance.mysql_instance.id -{%- if publicly_accessible %} - # By default, all IPs are authorized => 0.0.0.0/0 - acl_rules { - ip = "0.0.0.0/0" - description = "accessible from any host" - } -{%- else %} # TODO(benjaminch): Allow only Scaleway's private traffic acl_rules { ip = "0.0.0.0/0" description = "accessible from any host" } -{% endif %} depends_on = [ scaleway_rdb_instance.mysql_instance ] } +{% endif %} resource "scaleway_rdb_instance" "mysql_instance" { name = var.database_name diff --git a/lib/scaleway/services/postgresql/main.j2.tf b/lib/scaleway/services/postgresql/main.j2.tf index f222feec..0a737b05 100644 --- a/lib/scaleway/services/postgresql/main.j2.tf +++ b/lib/scaleway/services/postgresql/main.j2.tf @@ -13,25 +13,21 @@ locals { tags_postgresql_list = [for i, v in local.tags_postgresql : "${i}=${v}"] # NOTE: Scaleway doesn't support KV style tags } +{%- if publicly_accessible != false %} +# DB - ACL +# The initial setup of an instance allows full network access from anywhere (0.0.0.0/0). resource "scaleway_rdb_acl" "main" { - instance_id = scaleway_rdb_instance.postgresql_instance.id -{%- if publicly_accessible %} - # By default, all IPs are authorized => 0.0.0.0/0 - acl_rules { - ip = "0.0.0.0/0" - description = "accessible from any host" - } -{%- else %} + instance_id = scaleway_rdb_instance.mysql_instance.id # TODO(benjaminch): Allow only Scaleway's private traffic acl_rules { ip = "0.0.0.0/0" description = "accessible from any host" } -{% endif %} depends_on = [ scaleway_rdb_instance.postgresql_instance ] } +{% endif %} resource "scaleway_rdb_instance" "postgresql_instance" { name = var.database_name diff --git a/src/cloud_provider/aws/kubernetes/mod.rs b/src/cloud_provider/aws/kubernetes/mod.rs index c5bd41c3..5765fde3 100644 --- a/src/cloud_provider/aws/kubernetes/mod.rs +++ b/src/cloud_provider/aws/kubernetes/mod.rs @@ -24,7 +24,8 @@ use crate::cloud_provider::qovery::EngineLocation; use crate::cloud_provider::{kubernetes, CloudProvider}; use crate::cmd; use crate::cmd::kubectl::{ - kubectl_exec_api_custom_metrics, kubectl_exec_get_all_namespaces, kubectl_exec_scale_replicas, ScalingKind, + kubectl_exec_api_custom_metrics, kubectl_exec_get_all_namespaces, kubectl_exec_get_events, + kubectl_exec_scale_replicas, ScalingKind, }; use crate::cmd::structs::HelmChart; use crate::cmd::terraform::{terraform_exec, terraform_init_validate_plan_apply, terraform_init_validate_state_list}; @@ -958,7 +959,20 @@ impl<'a> Kubernetes for EKS<'a> { } fn on_create_error(&self) -> Result<(), EngineError> { + let kubeconfig_file = match self.config_file() { + Ok(x) => x.0, + Err(e) => { + error!("kubernetes cluster has just been deployed, but kubeconfig wasn't available, can't finish installation"); + return Err(e); + } + }; + let kubeconfig = PathBuf::from(&kubeconfig_file); + let environment_variables: Vec<(&str, &str)> = self.cloud_provider.credentials_environment_variables(); warn!("EKS.on_create_error() called for {}", self.name()); + match kubectl_exec_get_events(kubeconfig, None, environment_variables) { + Ok(_x) => (), + Err(_e) => (), + }; Err(self.engine_error( EngineErrorCause::Internal, format!("{} Kubernetes cluster failed on deployment", self.name()), diff --git a/src/cloud_provider/helm.rs b/src/cloud_provider/helm.rs index d95865fb..1c3b945d 100644 --- a/src/cloud_provider/helm.rs +++ b/src/cloud_provider/helm.rs @@ -214,7 +214,7 @@ pub trait HelmChart: Send { let environment_variables: Vec<(&str, &str)> = envs.iter().map(|x| (x.0.as_str(), x.1.as_str())).collect(); kubectl_exec_get_events( kubernetes_config, - get_chart_namespace(self.get_chart_info().namespace).as_str(), + Some(get_chart_namespace(self.get_chart_info().namespace).as_str()), environment_variables, )?; Ok(payload) diff --git a/src/cloud_provider/scaleway/kubernetes/mod.rs b/src/cloud_provider/scaleway/kubernetes/mod.rs index 191cb934..0fe62b02 100644 --- a/src/cloud_provider/scaleway/kubernetes/mod.rs +++ b/src/cloud_provider/scaleway/kubernetes/mod.rs @@ -362,7 +362,7 @@ impl<'a> Kubernetes for Kapsule<'a> { } fn region(&self) -> &str { - self.zone.as_str() + self.zone.region_str() } fn zone(&self) -> &str { diff --git a/src/cmd/kubectl.rs b/src/cmd/kubectl.rs index c4d30d57..c1afd616 100644 --- a/src/cmd/kubectl.rs +++ b/src/cmd/kubectl.rs @@ -842,7 +842,7 @@ where pub fn kubectl_exec_get_events

( kubernetes_config: P, - namespace: &str, + namespace: Option<&str>, envs: Vec<(&str, &str)>, ) -> Result<(), SimpleError> where @@ -851,7 +851,11 @@ where let mut environment_variables = envs; environment_variables.push((KUBECONFIG, kubernetes_config.as_ref().to_str().unwrap())); - let args = vec!["get", "event", "-n", namespace, "--sort-by='.lastTimestamp'"]; + let mut args = vec!["get", "event", "-A", "--sort-by='.lastTimestamp'"]; + if !namespace.unwrap().is_empty() { + args = vec!["get", "event", "-n", namespace.unwrap(), "--sort-by='.lastTimestamp'"]; + } + kubectl_exec_with_output( args, environment_variables, diff --git a/src/container_registry/docker.rs b/src/container_registry/docker.rs new file mode 100644 index 00000000..6b7625cc --- /dev/null +++ b/src/container_registry/docker.rs @@ -0,0 +1,206 @@ +use crate::cmd; +use crate::container_registry::Kind; +use crate::error::{SimpleError, SimpleErrorKind}; +use chrono::Duration; +use retry::delay::Fibonacci; +use retry::Error::Operation; +use retry::OperationResult; + +#[derive(Default, Debug, Clone, PartialEq, serde_derive::Serialize, serde_derive::Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct DockerImageManifest { + pub schema_version: i64, + pub media_type: String, + pub config: Config, + pub layers: Vec, +} + +#[derive(Default, Debug, Clone, PartialEq, serde_derive::Serialize, serde_derive::Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct Config { + pub media_type: String, + pub size: i64, + pub digest: String, +} + +#[derive(Default, Debug, Clone, PartialEq, serde_derive::Serialize, serde_derive::Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct Layer { + pub media_type: String, + pub size: i64, + pub digest: String, +} + +pub fn docker_manifest_inspect( + container_registry_kind: Kind, + docker_envs: Vec<(&str, &str)>, + image_name: String, + image_tag: String, + registry_url: String, +) -> Option { + let image_with_tag = format!("{}:{}", image_name, image_tag); + let registry_provider = match container_registry_kind { + Kind::DockerHub => "DockerHub", + Kind::Ecr => "AWS ECR", + Kind::Docr => "DigitalOcean Registry", + Kind::ScalewayCr => "Scaleway Registry", + }; + + // Note: `docker manifest inspect` is still experimental for the time being: + // https://docs.docker.com/engine/reference/commandline/manifest_inspect/ + let mut envs = docker_envs.clone(); + envs.push(("DOCKER_CLI_EXPERIMENTAL", "enabled")); + + let binary = "docker"; + let image_full_url = format!("{}/{}", registry_url.as_str(), &image_with_tag); + let args = vec!["manifest", "inspect", image_full_url.as_str()]; + + return match cmd::utilities::exec_with_envs_and_output( + binary, + args.clone(), + envs.clone(), + |_| {}, + |_| {}, + Duration::minutes(1), + ) { + Ok(raw_output) => { + let joined = raw_output.join(""); + match serde_json::from_str(&joined) { + Ok(extracted_manifest) => Some(extracted_manifest), + Err(e) => { + error!( + "error while trying to deserialize manifest image manifest for image {} in {} ({}): {:?}", + image_with_tag, registry_provider, registry_url, e, + ); + None + } + } + } + Err(e) => { + error!( + "error while trying to inspect image manifest for image {} in {} ({}), command `{}`: {:?}", + image_with_tag, + registry_provider, + registry_url, + cmd::utilities::command_to_string(binary, &args, &envs), + e, + ); + None + } + }; +} + +pub fn docker_login( + container_registry_kind: Kind, + docker_envs: Vec<(&str, &str)>, + registry_login: String, + registry_pass: String, + registry_url: String, +) -> Result<(), SimpleError> { + let registry_provider = match container_registry_kind { + Kind::DockerHub => "DockerHub", + Kind::Ecr => "AWS ECR", + Kind::Docr => "DigitalOcean Registry", + Kind::ScalewayCr => "Scaleway Registry", + }; + + let binary = "docker"; + let args = vec![ + "login", + registry_url.as_str(), + "-u", + registry_login.as_str(), + "-p", + registry_pass.as_str(), + ]; + + match cmd::utilities::exec(binary, args.clone(), &docker_envs.clone()) { + Ok(_) => Ok(()), + Err(e) => { + let error_message = format!( + "error while trying to login to registry {} {}, command `{}`: {:?}", + registry_provider, + registry_url, + cmd::utilities::command_to_string(binary, &args, &docker_envs), + e, + ); + error!("{}", error_message); + + Err(SimpleError::new(SimpleErrorKind::Other, Some(error_message))) + } + } +} + +pub fn docker_tag_and_push_image( + container_registry_kind: Kind, + docker_envs: Vec<(&str, &str)>, + image_name: String, + image_tag: String, + dest: String, +) -> Result<(), SimpleError> { + let image_with_tag = format!("{}:{}", image_name, image_tag); + let registry_provider = match container_registry_kind { + Kind::DockerHub => "DockerHub", + Kind::Ecr => "AWS ECR", + Kind::Docr => "DigitalOcean Registry", + Kind::ScalewayCr => "Scaleway Registry", + }; + + match retry::retry(Fibonacci::from_millis(3000).take(5), || { + match cmd::utilities::exec("docker", vec!["tag", &image_with_tag, dest.as_str()], &docker_envs) { + Ok(_) => OperationResult::Ok(()), + Err(e) => { + info!("failed to tag image {}, retrying...", image_with_tag); + OperationResult::Retry(e) + } + } + }) { + Err(Operation { error, .. }) => { + return Err(SimpleError::new( + SimpleErrorKind::Other, + Some(format!("failed to tag image {}: {:?}", image_with_tag, error.message)), + )) + } + _ => {} + } + + match retry::retry( + Fibonacci::from_millis(5000).take(5), + || match cmd::utilities::exec_with_envs_and_output( + "docker", + vec!["push", dest.as_str()], + docker_envs.clone(), + |line| { + let line_string = line.unwrap_or_default(); + info!("{}", line_string.as_str()); + }, + |line| { + let line_string = line.unwrap_or_default(); + error!("{}", line_string.as_str()); + }, + Duration::minutes(10), + ) { + Ok(_) => OperationResult::Ok(()), + Err(e) => { + warn!( + "failed to push image {} on {}, {:?} retrying...", + image_with_tag, registry_provider, e.message + ); + OperationResult::Retry(e) + } + }, + ) { + Err(Operation { error, .. }) => Err(error), + Err(e) => Err(SimpleError::new( + SimpleErrorKind::Other, + Some(format!( + "unknown error while trying to push image {} to {}. {:?}", + image_with_tag, registry_provider, e + )), + )), + _ => { + info!("image {} has successfully been pushed", image_with_tag); + Ok(()) + } + } +} diff --git a/src/container_registry/docker_hub.rs b/src/container_registry/docker_hub.rs index c5538e4b..3d622ec6 100644 --- a/src/container_registry/docker_hub.rs +++ b/src/container_registry/docker_hub.rs @@ -4,7 +4,7 @@ use reqwest::StatusCode; use crate::build_platform::Image; use crate::cmd; -use crate::container_registry::utilities::docker_tag_and_push_image; +use crate::container_registry::docker::docker_tag_and_push_image; use crate::container_registry::{ContainerRegistry, EngineError, Kind, PushResult}; use crate::error::EngineErrorCause; use crate::models::{ diff --git a/src/container_registry/docr.rs b/src/container_registry/docr.rs index d5f34892..2e87e0b8 100644 --- a/src/container_registry/docr.rs +++ b/src/container_registry/docr.rs @@ -4,7 +4,7 @@ use reqwest::StatusCode; use serde::{Deserialize, Serialize}; use crate::build_platform::Image; -use crate::container_registry::utilities::docker_tag_and_push_image; +use crate::container_registry::docker::docker_tag_and_push_image; use crate::container_registry::{ContainerRegistry, EngineError, Kind, PushResult}; use crate::error::{cast_simple_error_to_engine_error, EngineErrorCause, SimpleError, SimpleErrorKind}; use crate::models::{ diff --git a/src/container_registry/ecr.rs b/src/container_registry/ecr.rs index bd36201a..ba6aac66 100644 --- a/src/container_registry/ecr.rs +++ b/src/container_registry/ecr.rs @@ -10,7 +10,7 @@ use rusoto_sts::{GetCallerIdentityRequest, Sts, StsClient}; use crate::build_platform::Image; use crate::cmd; -use crate::container_registry::utilities::docker_tag_and_push_image; +use crate::container_registry::docker::docker_tag_and_push_image; use crate::container_registry::{ContainerRegistry, Kind, PushResult}; use crate::error::{EngineError, EngineErrorCause}; use crate::models::{ diff --git a/src/container_registry/mod.rs b/src/container_registry/mod.rs index 703dbbbb..dd10b3ad 100644 --- a/src/container_registry/mod.rs +++ b/src/container_registry/mod.rs @@ -4,11 +4,11 @@ use crate::build_platform::Image; use crate::error::{EngineError, EngineErrorCause, EngineErrorScope}; use crate::models::{Context, Listen}; +pub mod docker; pub mod docker_hub; pub mod docr; pub mod ecr; pub mod scaleway_container_registry; -pub mod utilities; pub trait ContainerRegistry: Listen { fn context(&self) -> &Context; diff --git a/src/container_registry/scaleway_container_registry.rs b/src/container_registry/scaleway_container_registry.rs index 6d9c8a00..8480afc3 100644 --- a/src/container_registry/scaleway_container_registry.rs +++ b/src/container_registry/scaleway_container_registry.rs @@ -3,8 +3,7 @@ extern crate scaleway_api_rs; use crate::cloud_provider::scaleway::application::Zone; use crate::build_platform::Image; -use crate::cmd; -use crate::container_registry::utilities::docker_tag_and_push_image; +use crate::container_registry::docker::{docker_login, docker_manifest_inspect, docker_tag_and_push_image}; use crate::container_registry::{ContainerRegistry, Kind, PushResult}; use crate::error::{EngineError, EngineErrorCause}; use crate::models::{ @@ -14,12 +13,14 @@ use crate::runtime::block_on; use retry::delay::Fibonacci; use retry::Error::Operation; use retry::OperationResult; +use rusoto_core::param::ToParam; pub struct ScalewayCR { context: Context, id: String, name: String, default_project_id: String, + login: String, secret_token: String, zone: Zone, listeners: Listeners, @@ -39,6 +40,7 @@ impl ScalewayCR { id: id.to_string(), name: name.to_string(), default_project_id: default_project_id.to_string(), + login: "nologin".to_string(), secret_token: secret_token.to_string(), zone, listeners: Vec::new(), @@ -333,7 +335,30 @@ impl ContainerRegistry for ScalewayCR { } fn does_image_exists(&self, image: &Image) -> bool { - self.get_image(image).is_some() + let registry_url = image + .registry_url + .as_ref() + .unwrap_or(&"undefined".to_string()) + .to_param(); + + if let Err(_) = docker_login( + Kind::ScalewayCr, + self.get_docker_envs(), + self.login.clone(), + self.secret_token.clone(), + registry_url.clone(), + ) { + return false; + } + + docker_manifest_inspect( + Kind::ScalewayCr, + self.get_docker_envs(), + image.name.clone(), + image.tag.clone(), + registry_url, + ) + .is_some() } fn push(&self, image: &Image, force_push: bool) -> Result { @@ -364,19 +389,12 @@ impl ContainerRegistry for ScalewayCR { } } - let envs = self.get_docker_envs(); - - if cmd::utilities::exec( - "docker", - vec![ - "login", - registry_url.as_str(), - "-u", - "nologin", - "-p", - self.secret_token.as_str(), - ], - &envs, + if docker_login( + Kind::ScalewayCr, + self.get_docker_envs(), + self.login.clone(), + self.secret_token.clone(), + registry_url.clone(), ) .is_err() { diff --git a/src/container_registry/utilities.rs b/src/container_registry/utilities.rs deleted file mode 100644 index 21824e48..00000000 --- a/src/container_registry/utilities.rs +++ /dev/null @@ -1,81 +0,0 @@ -use crate::cmd; -use crate::container_registry::Kind; -use crate::error::{SimpleError, SimpleErrorKind}; -use chrono::Duration; -use retry::delay::Fibonacci; -use retry::Error::Operation; -use retry::OperationResult; - -pub fn docker_tag_and_push_image( - container_registry_kind: Kind, - docker_envs: Vec<(&str, &str)>, - image_name: String, - image_tag: String, - dest: String, -) -> Result<(), SimpleError> { - let image_with_tag = format!("{}:{}", image_name, image_tag); - let registry_provider = match container_registry_kind { - Kind::DockerHub => "DockerHub", - Kind::Ecr => "AWS ECR", - Kind::Docr => "DigitalOcean Registry", - Kind::ScalewayCr => "Scaleway Registry", - }; - - match retry::retry(Fibonacci::from_millis(3000).take(5), || { - match cmd::utilities::exec("docker", vec!["tag", &image_with_tag, dest.as_str()], &docker_envs) { - Ok(_) => OperationResult::Ok(()), - Err(e) => { - info!("failed to tag image {}, retrying...", image_with_tag); - OperationResult::Retry(e) - } - } - }) { - Err(Operation { error, .. }) => { - return Err(SimpleError::new( - SimpleErrorKind::Other, - Some(format!("failed to tag image {}: {:?}", image_with_tag, error.message)), - )) - } - _ => {} - } - - match retry::retry( - Fibonacci::from_millis(5000).take(5), - || match cmd::utilities::exec_with_envs_and_output( - "docker", - vec!["push", dest.as_str()], - docker_envs.clone(), - |line| { - let line_string = line.unwrap_or_default(); - info!("{}", line_string.as_str()); - }, - |line| { - let line_string = line.unwrap_or_default(); - error!("{}", line_string.as_str()); - }, - Duration::minutes(10), - ) { - Ok(_) => OperationResult::Ok(()), - Err(e) => { - warn!( - "failed to push image {} on {}, {:?} retrying...", - image_with_tag, registry_provider, e.message - ); - OperationResult::Retry(e) - } - }, - ) { - Err(Operation { error, .. }) => Err(error), - Err(e) => Err(SimpleError::new( - SimpleErrorKind::Other, - Some(format!( - "unknown error while trying to push image {} to {}. {:?}", - image_with_tag, registry_provider, e - )), - )), - _ => { - info!("image {} has successfully been pushed", image_with_tag); - Ok(()) - } - } -} diff --git a/tests/scaleway/scw_databases.rs b/tests/scaleway/scw_databases.rs index 5567c87e..5d23e75c 100644 --- a/tests/scaleway/scw_databases.rs +++ b/tests/scaleway/scw_databases.rs @@ -624,6 +624,7 @@ fn postgresql_v12_deploy_a_working_dev_environment() { #[cfg(feature = "test-scw-managed-services")] #[named] #[test] +#[ignore] fn postgresql_v10_deploy_a_working_prod_environment() { let context = context(); let secrets = FuncTestsSecrets::new(); @@ -642,6 +643,7 @@ fn postgresql_v10_deploy_a_working_prod_environment() { #[cfg(feature = "test-scw-managed-services")] #[named] #[test] +#[ignore] fn postgresql_v11_deploy_a_working_prod_environment() { let context = context(); let secrets = FuncTestsSecrets::new(); @@ -660,6 +662,7 @@ fn postgresql_v11_deploy_a_working_prod_environment() { #[cfg(feature = "test-scw-managed-services")] #[named] #[test] +#[ignore] fn postgresql_v12_deploy_a_working_prod_environment() { let context = context(); let secrets = FuncTestsSecrets::new(); @@ -1020,6 +1023,7 @@ fn mysql_v8_deploy_a_working_dev_environment() { #[cfg(feature = "test-scw-managed-services")] #[named] #[test] +#[ignore] fn mysql_v8_deploy_a_working_prod_environment() { let context = context(); let secrets = FuncTestsSecrets::new();