Skip to content

Commit

Permalink
VCS and K8s tf integration (#6)
Browse files Browse the repository at this point in the history
* VCS and K8s tf integration
* GitOps parametrisation and push

---------

Co-authored-by: Serg Shalavin <[email protected]>
  • Loading branch information
all4code and sergs-pci authored Sep 19, 2023
1 parent b32258d commit 150aaa4
Show file tree
Hide file tree
Showing 57 changed files with 920 additions and 305 deletions.
14 changes: 7 additions & 7 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
.idea
**/*.env
**/.env
**/.terraform/
**/.terraform.lock.hcl
**/.terraform.tfstate.lock.info
**/terraform.tfstate
**/terraform.tfstate.backup
*.env
.env
.terraform/
.terraform.lock.hcl
.terraform.tfstate.lock.info
terraform.tfstate
terraform.tfstate.backup
.vscode
.DS_Store
2 changes: 1 addition & 1 deletion cli/.flake8
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
flake8]
[flake8]
extend-ignore = E722,W504,E501,Q000,N802,D401,D413
exclude =
venv,
Expand Down
283 changes: 248 additions & 35 deletions cli/commands/setup.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import os
from pathlib import Path

import click
import yaml

from cli.common.const.const import GITOPS_REPOSITORY_URL, GITOPS_REPOSITORY_BRANCH
from cli.common.const.const import GITOPS_REPOSITORY_URL, GITOPS_REPOSITORY_BRANCH, LOCAL_FOLDER
from cli.common.const.parameter_names import *
from cli.common.enums.cloud_providers import CloudProviders
from cli.common.enums.dns_registrars import DnsRegistrars
Expand All @@ -15,8 +18,10 @@
from cli.services.dns.dns_provider_manager import DNSManager
from cli.services.dns.route53.route53 import Route53Manager
from cli.services.keys.key_manager import KeyManager
from cli.services.tf_wrapper import TfWrapper
from cli.services.vcs.git_provider_manager import GitProviderManager
from cli.services.vcs.github.github_manager import GitHubProviderManager
from cli.services.vcs.template_manager import GitOpsTemplateManager


@click.command()
Expand Down Expand Up @@ -53,7 +58,7 @@ def setup(email: str, cloud_provider: CloudProviders, cloud_profile: str, cloud_
dns_reg_secret: str, domain: str, git_provider: GitProviders, git_org: str, git_token: str,
gitops_repo_name: str, gitops_template_url: str, gitops_template_branch: str, install_demo: bool, config):
"""Creates new CG DevX installation."""
click.echo("Setup CG DevX installation.")
click.echo("Setup CG DevX installation...")

p: StateStore
if config is not None:
Expand Down Expand Up @@ -91,10 +96,10 @@ def setup(email: str, cloud_provider: CloudProviders, cloud_profile: str, cloud_

# validate parameters
p.validate_input_params(validator=setup_param_validator)

# save checkpoint
p.save_checkpoint()

click.echo("Executing pre-flight checks")
# init proper cloud provider
if p.cloud_provider == CloudProviders.AWS:
cm: CloudProviderManager = AWSManager(p.get_input_param(CLOUD_REGION),
Expand All @@ -119,57 +124,265 @@ def setup(email: str, cloud_provider: CloudProviders, cloud_profile: str, cloud_
if p.cloud_provider == CloudProviders.Azure:
cm: CloudProviderManager = AzureManager()

cloud_provider_check(cm, p)
click.echo("Cloud provider pre-flight check. Done!")
p.parameters["<CLOUD_REGION>"] = cm.region

# init proper git provider
if p.git_provider == GitProviders.GitHub:
gm: GitProviderManager = GitHubProviderManager(p.get_input_param(GIT_ACCESS_TOKEN),
p.get_input_param(GIT_ORGANIZATION_NAME))

git_provider_check(gm, p)
click.echo("Git provider pre-flight check. Done!")

# init proper dns registrar provider
# Note!: Route53 is initialised with AWS Cloud Provider

dns_provider_check(dm, p)
click.echo("DNS provider pre-flight check. Done!")
if not p.has_checkpoint("preflight"):
click.echo("Executing pre-flight checks...")

# create ssh keys
click.echo("Generating ssh keys")
public_key = KeyManager.create_keys()
click.echo("Generating ssh keys. Done!")
cloud_provider_check(cm, p)
click.echo("Cloud provider pre-flight check. Done!")

# create terraform storage backend
click.echo("Creating tf backend storage")
tf_backend_storage_name: str = f'{p.get_input_param(GITOPS_REPOSITORY_NAME)}-{random_string_generator()}'.lower()
tf_backend_location = cm.create_iac_state_storage(tf_backend_storage_name)
click.echo("Creating tf backend storage. Done!")
# tf_backend_location = 'http://cg-devx-gitops-lf49vhtx.s3.amazonaws.com/'
git_provider_check(gm, p)
click.echo("Git provider pre-flight check. Done!")

p.set_checkpoint("preflight")
p.save_checkpoint()
git_user_login, git_user_name, git_user_email = gm.get_current_user_info()
p.internals["GIT_USER_LOGIN"] = git_user_login
p.internals["GIT_USER_NAME"] = git_user_name
p.internals["GIT_USER_EMAIL"] = git_user_email
p.parameters["# <GIT_PROVIDER_MODULE>"] = gm.create_tf_module_snippet()

dns_provider_check(dm, p)
click.echo("DNS provider pre-flight check. Done!")

# create ssh keys
click.echo("Generating ssh keys...")
default_public_key, public_key_path, private_key_path = KeyManager.create_ed_keys()
p.parameters["<VCS_BOT_SSH_PUBLIC_KEY>"] = default_public_key
p.internals["DEFAULT_SSH_PUBLIC_KEY_PATH"] = public_key_path
p.internals["DEFAULT_SSH_PRIVATE_KEY_PATH"] = private_key_path

# Optional K8s cluster keys
k8s_public_key, k8s_public_key_path, k8s_private_key_path = KeyManager.create_rsa_keys()
p.parameters["<CC_CLUSTER_SSH_PUBLIC_KEY>"] = k8s_public_key
p.internals["CLUSTER_SSH_PUBLIC_KEY_PATH"] = k8s_public_key_path
p.internals["CLUSTER_SSH_PRIVATE_KEY_PATH"] = k8s_private_key_path

click.echo("Generating ssh keys. Done!")

click.echo("Checking dependencies")
# create terraform storage backend
click.echo("Creating tf backend storage...")
tf_backend_storage_name: str = f'{p.get_input_param(GITOPS_REPOSITORY_NAME)}-{random_string_generator()}'.lower()

tf_backend_location = cm.create_iac_state_storage(tf_backend_storage_name)

p.parameters["# <TF_VCS_REMOTE_BACKEND>"] = cm.create_iac_backend_snippet(tf_backend_storage_name, cm.region,
"vcs")
p.parameters["# <TF_HOSTING_REMOTE_BACKEND>"] = cm.create_iac_backend_snippet(tf_backend_storage_name,
cm.region,
"hosting_provider")
p.parameters["# <TF_HOSTING_PROVIDER>"] = cm.create_hosting_provider_snippet()

p.parameters["<K8S_AWS_SERVICE_ACCOUNT_ROLE_MAPPING>"] = cm.create_k8s_rol_binding_snippet()

click.echo("Creating tf backend storage. Done!")

p.set_checkpoint("preflight")
p.save_checkpoint()
click.echo("Pre-flight checks. Done!")

# end preflight check section
else:
click.echo("Skipped pre-flight checks.")

dm: DependencyManager = DependencyManager()

# terraform
if dm.check_tf():
click.echo("tf is installed. Continuing")
if not p.has_checkpoint("dependencies"):
click.echo("Dependencies check...")

# terraform
if dm.check_tf():
click.echo("tf is installed. Continuing...")
else:
click.echo("Downloading and installing tf...")
dm.install_tf()
click.echo("tf is installed.")

# kubectl
if dm.check_kubectl():
click.echo("kubectl is installed. Continuing...")
else:
click.echo("Downloading and installing kubectl...")
dm.install_kubectl()
click.echo("kubectl is installed.")

click.echo("Dependencies check. Done!")
p.set_checkpoint("dependencies")
p.save_checkpoint()
else:
click.echo("Downloading and installing tf")
dm.install_tf()
click.echo("tf is installed")
click.echo("Skipped dependencies check.")

# promote input params
# TODO: move to appropriate place
p.parameters["<OWNER_EMAIL>"] = p.get_input_param(OWNER_EMAIL)
p.parameters["<CLOUD_PROVIDER>"] = p.cloud_provider
p.parameters["<PRIMARY_CLUSTER_NAME>"] = p.get_input_param(PRIMARY_CLUSTER_NAME)
p.parameters["<GIT_PROVIDER>"] = p.git_provider
p.parameters["<GITOPS_REPOSITORY_NAME>"] = p.get_input_param(GITOPS_REPOSITORY_NAME)
p.parameters["<GIT_ORGANIZATION_NAME>"] = p.get_input_param(GIT_ORGANIZATION_NAME)
p.parameters["<DOMAIN_NAME>"] = p.get_input_param(DOMAIN_NAME)
p.parameters["<GIT_REPOSITORY_ROOT>"] = f'github.com/{p.get_input_param(GIT_ORGANIZATION_NAME)}/*'

p.parameters["<ATLANTIS_WEBHOOK_SECRET>"] = random_string_generator(20)

# Ingress URLs for core components. Note!: URL does not contain protocol
cluster_fqdn = f'{p.get_input_param(PRIMARY_CLUSTER_NAME)}.{p.get_input_param(DOMAIN_NAME)}'
p.parameters["<CC_CLUSTER_FQDN>"] = cluster_fqdn
p.parameters["<VAULT_INGRESS_URL>"] = f'vault.{cluster_fqdn}'
p.parameters["<ARGO_CD_INGRESS_URL>"] = f'argocd.{cluster_fqdn}'
p.parameters["<ARGO_WORKFLOW_INGRESS_URL>"] = f'argo.{cluster_fqdn}'
p.parameters["<ATLANTIS_INGRESS_URL>"] = f'atlantis.{cluster_fqdn}'
p.parameters["<HARBOR_INGRESS_URL>"] = f'harbor.{cluster_fqdn}'
p.parameters["<GRAFANA_INGRESS_URL>"] = f'grafana.{cluster_fqdn}'
p.parameters["<SONARQUBE_INGRESS_URL>"] = f'sonarqube.{cluster_fqdn}'

# OIDC config
vault_i = p.parameters["<VAULT_INGRESS_URL>"]
p.parameters["<OIDC_PROVIDER_URL>"] = f'{vault_i}/v1/identity/oidc/provider/cgdevx'
p.parameters["<OIDC_PROVIDER_AUTHORIZE_URL>"] = f'{vault_i}/ui/vault/identity/oidc/provider/cgdevx/authorize'
p.parameters["<OIDC_PROVIDER_TOKEN_URL>"] = f'{vault_i}/v1/identity/oidc/provider/cgdevx/token'
p.parameters["<OIDC_PROVIDER_USERINFO_URL>"] = f'{vault_i}/v1/identity/oidc/provider/cgdevx/userinfo'

p.parameters["<ARGO_CD_OAUTH_CALLBACK_URL>"] = f'{p.parameters["<ARGO_CD_INGRESS_URL>"]}/oauth2/callback'
p.parameters["<HARBOR_REGISTRY_URL>"] = f'{p.parameters["<HARBOR_INGRESS_URL>"]}'

# kubectl
if dm.check_kubectl():
click.echo("kubectl is installed. Continuing")
p.save_checkpoint()

# params section end

tm = GitOpsTemplateManager(p.get_input_param(GITOPS_REPOSITORY_TEMPLATE_URL),
p.get_input_param(GITOPS_REPOSITORY_TEMPLATE_BRANCH),
p.get_input_param(GIT_ACCESS_TOKEN))

if not p.has_checkpoint("repo-prep"):
click.echo("Preparing your GitOps code...")

tm.check_repository_existence()
tm.clone()
tm.restructure_template()
tm.parametrise_tf(p.parameters)

p.set_checkpoint("repo-prep")
p.save_checkpoint()

click.echo("Preparing your GitOps code. Done!")
else:
click.echo("Skipped GitOps code prep.")

click.echo("Provisioning VCS...")
# use to enable tf debug
# "TF_LOG": "DEBUG", "TF_LOG_PATH": "/Users/a1m/.cgdevx/gitops/terraform/vcs/terraform.log",
# drop empty values
tf_env_vars = {k: v for k, v in {
"AWS_PROFILE": p.get_input_param(CLOUD_PROFILE),
"AWS_ACCESS_KEY_ID": p.get_input_param(CLOUD_ACCOUNT_ACCESS_KEY),
"AWS_SECRET_ACCESS_KEY": p.get_input_param(CLOUD_ACCOUNT_ACCESS_SECRET),
"AWS_DEFAULT_REGION": p.parameters["<CLOUD_REGION>"],
}.items() if v}
tf_folder = Path().home() / LOCAL_FOLDER / "gitops" / "terraform"

# VCS section
if not p.has_checkpoint("vcs-tf"):
# vcs env vars
vcs_tf_env_vars = tf_env_vars | {"GITHUB_TOKEN": p.get_input_param(GIT_ACCESS_TOKEN),
"GITHUB_OWNER": p.get_input_param(GIT_ORGANIZATION_NAME)}

# set envs as required by tf
for k, vault_i in vcs_tf_env_vars.items():
os.environ[k] = vault_i

tf_wrapper = TfWrapper(tf_folder / "vcs")
tf_wrapper.init()
tf_wrapper.apply({"atlantis_repo_webhook_secret": p.parameters["<ATLANTIS_WEBHOOK_SECRET>"],
"vcs_bot_ssh_public_key": p.parameters["<VCS_BOT_SSH_PUBLIC_KEY>"]})
vcs_out = tf_wrapper.output()

# store out params
p.parameters["<GIT_REPOSITORY_GIT_URL>"] = vcs_out["gitops_repo_ssh_clone_url"]

# unset envs as no longer needed
for k in vcs_tf_env_vars.keys():
os.environ.pop(k)

p.set_checkpoint("vcs-tf")
p.save_checkpoint()

click.echo("Provisioning VCS. Done!")
else:
click.echo("Skipped VCS provisioning.")

# K8s Cluster section
if not p.has_checkpoint("k8s-tf"):
click.echo("Provisioning K8s cluster...")

# run hosting provider tf to create K8s cluster
hp_tf_env_vars = {
**{}, # add vars here
**tf_env_vars}
# set envs as required by tf
for k, vault_i in hp_tf_env_vars.items():
os.environ[k] = vault_i

tf_wrapper = TfWrapper(tf_folder / "hosting_provider")
tf_wrapper.init()
tf_wrapper.apply()
hp_out = tf_wrapper.output()

# store out params
# network
p.parameters["<NETWORK_ID>"] = hp_out["network_id"]
# roles
p.parameters["<ARGO_WORKFLOW_IAM_ROLE_RN>"] = hp_out["iam_argoworkflow_role"]
p.parameters["<ARGO_CD_IAM_ROLE_RN>"] = hp_out["iam_argoworkflow_role"] # TODO: use own role
p.parameters["<ATLANTIS_IAM_ROLE_RN>"] = hp_out["atlantis_role"]
p.parameters["<CERT_MANAGER_IAM_ROLE_RN>"] = hp_out["cert_manager_role"]
p.parameters["<HARBOR_IAM_ROLE_RN>"] = hp_out["harbor_role"]
p.parameters["<EXTERNAL_DNS_IAM_ROLE_RN>"] = hp_out["external_dns_role"]
p.parameters["<VAULT_IAM_ROLE_RN>"] = hp_out["vault_role"]
# cluster
p.parameters["<CC_CLUSTER_ENDPOINT>"] = hp_out["cluster_endpoint"]
p.parameters["<CC_CLUSTER_OIDC_PROVIDER>"] = hp_out["cluster_oidc_provider"] # do we need it?

# unset envs as no longer needed
for k in hp_tf_env_vars.keys():
os.environ.pop(k)

p.set_checkpoint("k8s-tf")
p.save_checkpoint()

click.echo("Provisioning K8s cluster. Done!")
else:
click.echo("Downloading and installing kubectl")
dm.install_kubectl()
click.echo("kubectl is installed")
click.echo("Skipped VCS provisioning.")

if not p.has_checkpoint("gitops-vcs"):
tm.parametrise_registry(p.parameters)
tm.parametrise_root(p.parameters)

click.echo("Pushing GitOps code...")

tm.upload(p.parameters["<GIT_REPOSITORY_GIT_URL>"],
p.internals["DEFAULT_SSH_PRIVATE_KEY_PATH"],
p.internals["GIT_USER_NAME"],
p.internals["GIT_USER_EMAIL"])

click.echo("Pushing GitOps code. Done!")

p.set_checkpoint("gitops-vcs")
p.save_checkpoint()
else:
click.echo("Skipped GitOps repo initialization.")

# user could get kubeconfig by running command
# `aws eks update-kubeconfig --region region-code --name my-cluster --kubeconfig my-config-path`
# CLI could not follow this approach as aws client could be not configured properly when keys are used
# CLI is creating this file programmatically

return True

Expand Down
6 changes: 5 additions & 1 deletion cli/common/const/const.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
DEFAULT_ENUM_VALUE = 'unknown'
GITOPS_REPOSITORY_URL = 'https://github.com/CloudGeometry/cg-devx-core.git'
GITOPS_REPOSITORY_URL = 'https://github.com/CloudGeometry/cgdevx-core.git'
GITOPS_REPOSITORY_BRANCH = 'main'
STATE_INPUT_PARAMS = 'input'
STATE_INTERNAL_PARAMS = 'internal'
STATE_PARAMS = 'params'
STATE_CHECKPOINTS = 'checkpoints'
LOCAL_FOLDER = '.cgdevx'
FALLBACK_AUTHOR_NAME = 'cgdevx-bot'
FALLBACK_AUTHOR_EMAIL = '[email protected]'
Loading

0 comments on commit 150aaa4

Please sign in to comment.