diff --git a/.github/workflows/deployment.yml b/.github/workflows/deployment.yml index ee62eb20c1..70520108d9 100644 --- a/.github/workflows/deployment.yml +++ b/.github/workflows/deployment.yml @@ -24,6 +24,13 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 + - name: black all files + run: | + python3 -m pip install black + pushd deployments/sequencer + ./black.sh --check + popd + - run: | # Install deps. npm install -g cdk8s-cli diff --git a/deployments/sequencer/app/service.py b/deployments/sequencer/app/service.py index 17388ef14c..f436fabcbf 100644 --- a/deployments/sequencer/app/service.py +++ b/deployments/sequencer/app/service.py @@ -9,73 +9,61 @@ from services import topology -class ServiceApp(Construct): +class ServiceApp(Construct): def __init__( - self, - scope: Construct, - id: str, - *, - namespace: str, - topology: topology.ServiceTopology + self, scope: Construct, id: str, *, namespace: str, topology: topology.ServiceTopology ): super().__init__(scope, id) self.namespace = namespace self.label = {"app": Names.to_label_value(self, include_hash=False)} self.topology = topology - + self.set_k8s_namespace() if topology.service is not None: self.set_k8s_service() - + if topology.config is not None: self.set_k8s_configmap() - + if topology.deployment is not None: self.set_k8s_deployment() if topology.ingress is not None: self.set_k8s_ingress() - + if topology.pvc is not None: self.set_k8s_pvc() def set_k8s_namespace(self): - return k8s.KubeNamespace( - self, - "namespace", - metadata=k8s.ObjectMeta( - name=self.namespace - ) - ) + return k8s.KubeNamespace(self, "namespace", metadata=k8s.ObjectMeta(name=self.namespace)) def set_k8s_configmap(self): return k8s.KubeConfigMap( self, "configmap", - metadata=k8s.ObjectMeta( - name=f"{self.node.id}-config" - ), + metadata=k8s.ObjectMeta(name=f"{self.node.id}-config"), data=dict(config=json.dumps(self.topology.config.get())), ) - + def set_k8s_service(self): return k8s.KubeService( - self, - "service", - spec=k8s.ServiceSpec( - type=self.topology.service.type.value, - ports=[ - k8s.ServicePort( - name=port.name, - port=port.port, - target_port=k8s.IntOrString.from_number(port.container_port), - ) for port in self.topology.service.ports - ], - selector=self.label - ) - ) + self, + "service", + spec=k8s.ServiceSpec( + type=self.topology.service.type.value, + ports=[ + k8s.ServicePort( + name=port.name, + port=port.port, + target_port=k8s.IntOrString.from_number(port.container_port), + ) + for port in self.topology.service.ports + ], + selector=self.label, + ), + ) def set_k8s_deployment(self): return k8s.KubeDeployment( @@ -87,9 +75,7 @@ def set_k8s_deployment(self): template=k8s.PodTemplateSpec( metadata=k8s.ObjectMeta(labels=self.label), spec=k8s.PodSpec( - security_context=k8s.PodSecurityContext( - fs_group=1000 - ), + security_context=k8s.PodSecurityContext(fs_group=1000), containers=[ k8s.Container( name=f"{self.node.id}-{container.name}", @@ -97,76 +83,98 @@ def set_k8s_deployment(self): # command=["sleep", "infinity"], args=container.args, ports=[ - k8s.ContainerPort( - container_port=port.container_port - ) for port in container.ports + k8s.ContainerPort(container_port=port.container_port) + for port in container.ports ], startup_probe=k8s.Probe( http_get=k8s.HttpGetAction( path=container.startup_probe.path, - port=k8s.IntOrString.from_string(container.startup_probe.port) - if isinstance(container.startup_probe.port, str) - else k8s.IntOrString.from_number(container.startup_probe.port) + port=k8s.IntOrString.from_string( + container.startup_probe.port + ) + if isinstance(container.startup_probe.port, str) + else k8s.IntOrString.from_number( + container.startup_probe.port + ), ), period_seconds=container.startup_probe.period_seconds, failure_threshold=container.startup_probe.failure_threshold, - timeout_seconds=container.startup_probe.timeout_seconds - ) if container.startup_probe is not None else None, - + timeout_seconds=container.startup_probe.timeout_seconds, + ) + if container.startup_probe is not None + else None, readiness_probe=k8s.Probe( http_get=k8s.HttpGetAction( path=container.readiness_probe.path, - port=k8s.IntOrString.from_string(container.readiness_probe.port) - if isinstance(container.readiness_probe.port, str) - else k8s.IntOrString.from_number(container.readiness_probe.port) + port=k8s.IntOrString.from_string( + container.readiness_probe.port + ) + if isinstance(container.readiness_probe.port, str) + else k8s.IntOrString.from_number( + container.readiness_probe.port + ), ), period_seconds=container.readiness_probe.period_seconds, failure_threshold=container.readiness_probe.failure_threshold, - timeout_seconds=container.readiness_probe.timeout_seconds - ) if container.readiness_probe is not None else None, - + timeout_seconds=container.readiness_probe.timeout_seconds, + ) + if container.readiness_probe is not None + else None, liveness_probe=k8s.Probe( http_get=k8s.HttpGetAction( path=container.liveness_probe.path, - port=k8s.IntOrString.from_string(container.liveness_probe.port) - if isinstance(container.liveness_probe.port, str) - else k8s.IntOrString.from_number(container.liveness_probe.port) + port=k8s.IntOrString.from_string( + container.liveness_probe.port + ) + if isinstance(container.liveness_probe.port, str) + else k8s.IntOrString.from_number( + container.liveness_probe.port + ), ), period_seconds=container.liveness_probe.period_seconds, failure_threshold=container.liveness_probe.failure_threshold, - timeout_seconds=container.liveness_probe.timeout_seconds - ) if container.liveness_probe is not None else None, - + timeout_seconds=container.liveness_probe.timeout_seconds, + ) + if container.liveness_probe is not None + else None, volume_mounts=[ k8s.VolumeMount( name=mount.name, mount_path=mount.mount_path, - read_only=mount.read_only - ) for mount in container.volume_mounts - ] - ) for container in self.topology.deployment.containers + read_only=mount.read_only, + ) + for mount in container.volume_mounts + ], + ) + for container in self.topology.deployment.containers ], volumes=list( chain( ( k8s.Volume( - name=f"{self.node.id}-{volume.name}", + name=f"{self.node.id}-{volume.name}", config_map=k8s.ConfigMapVolumeSource( name=f"{self.node.id}-{volume.name}" - ) - ) for volume in self.topology.deployment.configmap_volumes - ) if self.topology.deployment.configmap_volumes is not None else None, + ), + ) + for volume in self.topology.deployment.configmap_volumes + ) + if self.topology.deployment.configmap_volumes is not None + else None, ( k8s.Volume( name=f"{self.node.id}-{volume.name}", persistent_volume_claim=k8s.PersistentVolumeClaimVolumeSource( claim_name=f"{self.node.id}-{volume.name}", - read_only=volume.read_only - ) - ) for volume in self.topology.deployment.pvc_volumes - ) if self.topology.deployment is not None else None + read_only=volume.read_only, + ), + ) + for volume in self.topology.deployment.pvc_volumes + ) + if self.topology.deployment is not None + else None, ) - ) + ), ), ), ), @@ -179,15 +187,12 @@ def set_k8s_ingress(self): metadata=k8s.ObjectMeta( name=f"{self.node.id}-ingress", labels=self.label, - annotations=self.topology.ingress.annotations + annotations=self.topology.ingress.annotations, ), spec=k8s.IngressSpec( ingress_class_name=self.topology.ingress.class_name, tls=[ - k8s.IngressTls( - hosts=tls.hosts, - secret_name=tls.secret_name - ) + k8s.IngressTls(hosts=tls.hosts, secret_name=tls.secret_name) for tls in self.topology.ingress.tls or [] ], rules=[ @@ -203,45 +208,39 @@ def set_k8s_ingress(self): name=path.backend_service_name, port=k8s.ServiceBackendPort( number=path.backend_service_port_number - ) + ), ) - ) + ), ) for path in rule.paths or [] ] - ) + ), ) for rule in self.topology.ingress.rules or [] - ] - ) + ], + ), ) def set_k8s_pvc(self): k8s.KubePersistentVolumeClaim( self, "pvc", - metadata=k8s.ObjectMeta( - name=f"{self.node.id}-data", - labels=self.label - ), + metadata=k8s.ObjectMeta(name=f"{self.node.id}-data", labels=self.label), spec=k8s.PersistentVolumeClaimSpec( storage_class_name=self.topology.pvc.storage_class_name, access_modes=self.topology.pvc.access_modes, volume_mode=self.topology.pvc.volume_mode, resources=k8s.ResourceRequirements( requests={"storage": k8s.Quantity.from_string(self.topology.pvc.storage)} - ) - ) + ), + ), ) def set_k8s_backend_config(self): return google.BackendConfig( self, "backendconfig", - metadata=ApiObjectMetadata( - name=f"{self.node.id}-backendconfig", - labels=self.label - ), + metadata=ApiObjectMetadata(name=f"{self.node.id}-backendconfig", labels=self.label), spec=google.BackendConfigSpec( health_check=google.BackendConfigSpecHealthCheck( check_interval_sec=5, @@ -249,13 +248,13 @@ def set_k8s_backend_config(self): unhealthy_threshold=5, timeout_sec=5, request_path="/", - type="http" + type="http", ), iap=google.BackendConfigSpecIap( enabled=True, oauthclient_credentials=google.BackendConfigSpecIapOauthclientCredentials( secret_name="" - ) - ) - ) + ), + ), + ), ) diff --git a/deployments/sequencer/black.sh b/deployments/sequencer/black.sh new file mode 100755 index 0000000000..430650f5dd --- /dev/null +++ b/deployments/sequencer/black.sh @@ -0,0 +1,15 @@ +#!/bin/bash + +# Run black on the sequencer deployment project. + +DEFAULT_ARGS="-l 100 -t py39 --exclude imports" +function fix() { + black ${DEFAULT_ARGS} . +} + +function check() { + black --diff --color ${DEFAULT_ARGS} . +} + +[[ $1 == "--fix" ]] && fix +[[ $1 == "--check" ]] && check diff --git a/deployments/sequencer/config/sequencer.py b/deployments/sequencer/config/sequencer.py index be56e9272f..57ad681587 100644 --- a/deployments/sequencer/config/sequencer.py +++ b/deployments/sequencer/config/sequencer.py @@ -5,18 +5,19 @@ from services.objects import Config -ROOT_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), '../../../') -CONFIG_DIR = os.path.join(ROOT_DIR, 'config/sequencer/') +ROOT_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), "../../../") +CONFIG_DIR = os.path.join(ROOT_DIR, "config/sequencer/") class SequencerDevConfig(Config): def __init__(self, mount_path: str, config_file_path: str = ""): super().__init__( - schema=json.loads(open(os.path.join(CONFIG_DIR, 'default_config.json'), 'r').read()), - config=json.loads(open(os.path.join(CONFIG_DIR, 'presets', 'config.json'), 'r').read()) if not config_file_path else json.loads(open(os.path.abspath(config_file_path)).read()), - mount_path=mount_path + schema=json.loads(open(os.path.join(CONFIG_DIR, "default_config.json"), "r").read()), + config=json.loads(open(os.path.join(CONFIG_DIR, "presets", "config.json"), "r").read()) + if not config_file_path + else json.loads(open(os.path.abspath(config_file_path)).read()), + mount_path=mount_path, ) - def validate(self): jsonschema.validate(self.config, schema=self.schema) diff --git a/deployments/sequencer/main.py b/deployments/sequencer/main.py index 09da3b83ed..fdcd20cf88 100644 --- a/deployments/sequencer/main.py +++ b/deployments/sequencer/main.py @@ -24,21 +24,11 @@ def __post_init__(self): class SequencerNode(Chart): def __init__( - self, - scope: Construct, - name: str, - namespace: str, - topology: topology.ServiceTopology + self, scope: Construct, name: str, namespace: str, topology: topology.ServiceTopology ): - super().__init__( - scope, name, disable_resource_name_hashes=True, namespace=namespace - ) - self.service = ServiceApp( - self, - name, - namespace=namespace, - topology=topology - ) + super().__init__(scope, name, disable_resource_name_hashes=True, namespace=namespace) + self.service = ServiceApp(self, name, namespace=namespace, topology=topology) + def main(): if helpers.args.env == "dev": @@ -46,15 +36,10 @@ def main(): elif helpers.args.env == "prod": system_preset = topology.SequencerProd() - app = App( - yaml_output_type=YamlOutputType.FOLDER_PER_CHART_FILE_PER_RESOURCE - ) + app = App(yaml_output_type=YamlOutputType.FOLDER_PER_CHART_FILE_PER_RESOURCE) SequencerNode( - scope=app, - name="sequencer-node", - namespace=helpers.args.namespace, - topology=system_preset + scope=app, name="sequencer-node", namespace=helpers.args.namespace, topology=system_preset ) app.synth() diff --git a/deployments/sequencer/services/const.py b/deployments/sequencer/services/const.py index 34fb300965..6b58abbdd8 100644 --- a/deployments/sequencer/services/const.py +++ b/deployments/sequencer/services/const.py @@ -2,9 +2,9 @@ # k8s service types class ServiceType(Enum): - CLUSTER_IP = "ClusterIP" - LOAD_BALANCER = "LoadBalancer" - NODE_PORT = "NodePort" + CLUSTER_IP = "ClusterIP" + LOAD_BALANCER = "LoadBalancer" + NODE_PORT = "NodePort" # k8s container ports diff --git a/deployments/sequencer/services/helpers.py b/deployments/sequencer/services/helpers.py index 554f02d12c..268d8dce55 100644 --- a/deployments/sequencer/services/helpers.py +++ b/deployments/sequencer/services/helpers.py @@ -5,23 +5,16 @@ def argument_parser(): parser = argparse.ArgumentParser() parser.add_argument( - "--namespace", - required=True, - type=str, - help="Required: Specify the Kubernetes namespace." + "--namespace", required=True, type=str, help="Required: Specify the Kubernetes namespace." ) parser.add_argument( - "--config-file", - type=str, - help="Optional: Path to sequencer configuration file." + "--config-file", type=str, help="Optional: Path to sequencer configuration file." ) parser.add_argument( - "--env", - default="dev", - type=str, - help="Optional: Specify the enironment (e.g., dev, prod)" + "--env", default="dev", type=str, help="Optional: Specify the enironment (e.g., dev, prod)" ) return parser.parse_args() + args = argument_parser() diff --git a/deployments/sequencer/services/objects.py b/deployments/sequencer/services/objects.py index 597011a62c..fc5152dde5 100644 --- a/deployments/sequencer/services/objects.py +++ b/deployments/sequencer/services/objects.py @@ -12,8 +12,7 @@ class Probe: timeout_seconds: int def __post_init__(self): - assert not isinstance(self.port, (bool)), \ - "Port must be of type int or str, not bool." + assert not isinstance(self.port, (bool)), "Port must be of type int or str, not bool." @dataclasses.dataclass @@ -100,7 +99,7 @@ class PvcVolume: read_only: bool -@ dataclasses.dataclass +@dataclasses.dataclass class ContainerPort: container_port: int diff --git a/deployments/sequencer/services/topology.py b/deployments/sequencer/services/topology.py index be9ebaa7c8..af034644a6 100644 --- a/deployments/sequencer/services/topology.py +++ b/deployments/sequencer/services/topology.py @@ -2,23 +2,31 @@ import typing -from services import ( - objects, - topology_helpers -) +from services import objects, topology_helpers @dataclasses.dataclass class ServiceTopology: - deployment: typing.Optional[objects.Deployment] = dataclasses.field(default_factory=topology_helpers.get_deployment) - config: typing.Optional[objects.Config] = dataclasses.field(default_factory=topology_helpers.get_config) - service: typing.Optional[objects.Service] = dataclasses.field(default_factory=topology_helpers.get_service) - pvc: typing.Optional[objects.PersistentVolumeClaim] = dataclasses.field(default_factory=topology_helpers.get_pvc) - ingress: typing.Optional[objects.Ingress] = dataclasses.field(default_factory=topology_helpers.get_ingress) + deployment: typing.Optional[objects.Deployment] = dataclasses.field( + default_factory=topology_helpers.get_deployment + ) + config: typing.Optional[objects.Config] = dataclasses.field( + default_factory=topology_helpers.get_config + ) + service: typing.Optional[objects.Service] = dataclasses.field( + default_factory=topology_helpers.get_service + ) + pvc: typing.Optional[objects.PersistentVolumeClaim] = dataclasses.field( + default_factory=topology_helpers.get_pvc + ) + ingress: typing.Optional[objects.Ingress] = dataclasses.field( + default_factory=topology_helpers.get_ingress + ) class SequencerDev(ServiceTopology): pass + class SequencerProd(SequencerDev): pass diff --git a/deployments/sequencer/services/topology_helpers.py b/deployments/sequencer/services/topology_helpers.py index 2a0a609162..2779138008 100644 --- a/deployments/sequencer/services/topology_helpers.py +++ b/deployments/sequencer/services/topology_helpers.py @@ -3,31 +3,30 @@ def get_pvc() -> objects.PersistentVolumeClaim: - return objects.PersistentVolumeClaim( - access_modes=["ReadWriteOnce"], - storage_class_name="premium-rwo", - volume_mode="Filesystem", - storage="64Gi", - mount_path="/data", - read_only=False + return objects.PersistentVolumeClaim( + access_modes=["ReadWriteOnce"], + storage_class_name="premium-rwo", + volume_mode="Filesystem", + storage="64Gi", + mount_path="/data", + read_only=False, ) def get_config() -> objects.Config: - return SequencerDevConfig( - mount_path="/config/sequencer/presets/", - config_file_path=helpers.args.config_file - ) + return SequencerDevConfig( + mount_path="/config/sequencer/presets/", config_file_path=helpers.args.config_file + ) def get_ingress() -> objects.Ingress: - return objects.Ingress( + return objects.Ingress( annotations={ "kubernetes.io/tls-acme": "true", "cert-manager.io/common-name": f"{helpers.args.namespace}.gcp-integration.sw-dev.io", "cert-manager.io/issue-temporary-certificate": "true", "cert-manager.io/issuer": "letsencrypt-prod", - "acme.cert-manager.io/http01-edit-in-place": "true" + "acme.cert-manager.io/http01-edit-in-place": "true", }, class_name=None, rules=[ @@ -38,86 +37,83 @@ def get_ingress() -> objects.Ingress: path="/monitoring/", path_type="Prefix", backend_service_name="sequencer-node-service", - backend_service_port_number=const.MONITORING_SERVICE_PORT + backend_service_port_number=const.MONITORING_SERVICE_PORT, ) - ] + ], ) ], tls=[ objects.IngressTls( - hosts=[ - f"{helpers.args.namespace}.gcp-integration.sw-dev.io" - ], - secret_name="sequencer-tls" + hosts=[f"{helpers.args.namespace}.gcp-integration.sw-dev.io"], + secret_name="sequencer-tls", ) - ] + ], ) def get_service() -> objects.Service: - return objects.Service( - type=const.ServiceType.CLUSTER_IP, - selector={}, - ports=[ - objects.PortMapping( - name="http", - port=const.HTTP_SERVICE_PORT, - container_port=const.HTTP_CONTAINER_PORT - ), - objects.PortMapping( - name="rpc", - port=const.RPC_SERVICE_PORT, - container_port=const.RPC_CONTAINER_PORT - ), - objects.PortMapping( - name="monitoring", - port=const.MONITORING_SERVICE_PORT, - container_port=const.MONITORING_CONTAINER_PORT - ) - ] - ) + return objects.Service( + type=const.ServiceType.CLUSTER_IP, + selector={}, + ports=[ + objects.PortMapping( + name="http", port=const.HTTP_SERVICE_PORT, container_port=const.HTTP_CONTAINER_PORT + ), + objects.PortMapping( + name="rpc", port=const.RPC_SERVICE_PORT, container_port=const.RPC_CONTAINER_PORT + ), + objects.PortMapping( + name="monitoring", + port=const.MONITORING_SERVICE_PORT, + container_port=const.MONITORING_CONTAINER_PORT, + ), + ], + ) def get_deployment() -> objects.Deployment: - return objects.Deployment( - replicas=1, - annotations={}, - containers=[ - objects.Container( - name="server", - image="us.gcr.io/starkware-dev/sequencer-node-test:0.0.1-dev.3", - args=["--config_file", "/config/sequencer/presets/config"], - ports=[ - objects.ContainerPort(container_port=const.HTTP_CONTAINER_PORT), - objects.ContainerPort(container_port=const.RPC_CONTAINER_PORT), - objects.ContainerPort(container_port=const.MONITORING_CONTAINER_PORT) + return objects.Deployment( + replicas=1, + annotations={}, + containers=[ + objects.Container( + name="server", + image="us.gcr.io/starkware-dev/sequencer-node-test:0.0.1-dev.3", + args=["--config_file", "/config/sequencer/presets/config"], + ports=[ + objects.ContainerPort(container_port=const.HTTP_CONTAINER_PORT), + objects.ContainerPort(container_port=const.RPC_CONTAINER_PORT), + objects.ContainerPort(container_port=const.MONITORING_CONTAINER_PORT), + ], + startup_probe=objects.Probe( + port=const.MONITORING_CONTAINER_PORT, + path="/monitoring/nodeVersion", + period_seconds=10, + failure_threshold=10, + timeout_seconds=5, + ), + readiness_probe=objects.Probe( + port=const.MONITORING_CONTAINER_PORT, + path="/monitoring/ready", + period_seconds=10, + failure_threshold=5, + timeout_seconds=5, + ), + liveness_probe=objects.Probe( + port=const.MONITORING_CONTAINER_PORT, + path="/monitoring/alive", + period_seconds=10, + failure_threshold=5, + timeout_seconds=5, + ), + volume_mounts=[ + objects.VolumeMount( + name="config", mount_path="/config/sequencer/presets/", read_only=True + ), + objects.VolumeMount(name="data", mount_path="/data", read_only=False), + ], + ) ], - startup_probe=objects.Probe(port=const.MONITORING_CONTAINER_PORT, path="/monitoring/nodeVersion", period_seconds=10, failure_threshold=10, timeout_seconds=5), - readiness_probe=objects.Probe(port=const.MONITORING_CONTAINER_PORT, path="/monitoring/ready", period_seconds=10, failure_threshold=5, timeout_seconds=5), - liveness_probe=objects.Probe(port=const.MONITORING_CONTAINER_PORT, path="/monitoring/alive", period_seconds=10, failure_threshold=5, timeout_seconds=5), - volume_mounts=[ - objects.VolumeMount( - name="config", - mount_path="/config/sequencer/presets/", - read_only=True - ), - objects.VolumeMount( - name="data", - mount_path="/data", - read_only=False - ) - ] - ) - ], - pvc_volumes=[ - objects.PvcVolume( - name="data", - read_only=False - ) - ], - configmap_volumes=[ - objects.ConfigMapVolume( - name="config" - ) - ] - ) + pvc_volumes=[objects.PvcVolume(name="data", read_only=False)], + configmap_volumes=[objects.ConfigMapVolume(name="config")], + )