Merge branch 'dev' into fix/scw-managed-services

This commit is contained in:
Benjamin
2021-10-20 10:56:56 +02:00
committed by GitHub
17 changed files with 282 additions and 126 deletions

View File

@@ -10,7 +10,7 @@ terraform {
}
vault = {
source = "hashicorp/vault"
version = "~> 2.18.0"
version = "~> 2.24.1"
}
local = {
source = "hashicorp/local"

View File

@@ -43,7 +43,7 @@ terraform {
}
vault = {
source = "hashicorp/vault"
version = "~> 2.18.0"
version = "~> 2.24.1"
}
}
required_version = ">= 0.14"

View File

@@ -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 }}

View File

@@ -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

View File

@@ -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

View File

@@ -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()),

View File

@@ -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)

View File

@@ -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 {

View File

@@ -842,7 +842,7 @@ where
pub fn kubectl_exec_get_events<P>(
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,

View File

@@ -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<Layer>,
}
#[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<DockerImageManifest> {
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(())
}
}
}

View File

@@ -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::{

View File

@@ -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::{

View File

@@ -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::{

View File

@@ -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;

View File

@@ -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<PushResult, EngineError> {
@@ -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()
{

View File

@@ -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(())
}
}
}

View File

@@ -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();