skyramp 0.4.30__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,23 @@
1
+ Metadata-Version: 2.1
2
+ Name: skyramp
3
+ Version: 0.4.30
4
+ Summary: module for leveraging skyramp cli functionality
5
+ Requires-Python: >=3.0
6
+ Description-Content-Type: text/markdown
7
+ Classifier: License :: OSI Approved :: MIT License
8
+
9
+ # Skyramp
10
+ Skyramp is a pip module that provides utility functions for leveraging [skyramp CLI](https://skyramp.dev/docs/commands/skyramp/) commands.
11
+
12
+ ## Installation
13
+ To install Skyramp, simply run the following command in your terminal:
14
+ ```
15
+ pip install skyramp
16
+ ```
17
+
18
+ ## Usage
19
+ Once you've installed Skyramp, you can import it into your project like this:
20
+ ```
21
+ import skyramp
22
+ ```
23
+
@@ -0,0 +1,14 @@
1
+ # Skyramp
2
+ Skyramp is a pip module that provides utility functions for leveraging [skyramp CLI](https://skyramp.dev/docs/commands/skyramp/) commands.
3
+
4
+ ## Installation
5
+ To install Skyramp, simply run the following command in your terminal:
6
+ ```
7
+ pip install skyramp
8
+ ```
9
+
10
+ ## Usage
11
+ Once you've installed Skyramp, you can import it into your project like this:
12
+ ```
13
+ import skyramp
14
+ ```
@@ -0,0 +1,13 @@
1
+ [build-system]
2
+ requires = ["flit_core >=3.2,<4"]
3
+ build-backend = "flit_core.buildapi"
4
+
5
+ [project]
6
+ name = "skyramp"
7
+ version = "0.4.30"
8
+ description = "module for leveraging skyramp cli functionality"
9
+ readme = "README.md"
10
+ requires-python = ">=3.0"
11
+ classifiers = [
12
+ "License :: OSI Approved :: MIT License"
13
+ ]
@@ -0,0 +1,7 @@
1
+ """
2
+ Provides utility functions and classes for leveraging Skyramp functionality.
3
+ """
4
+
5
+ from skyramp.client import _Client as Client
6
+ from skyramp.endpoint import _RestEndpoint as RestEndpoint
7
+ from skyramp.endpoint import _GrpcEndpoint as GrpcEndpoint
@@ -0,0 +1,183 @@
1
+ """
2
+ Defines a Skyramp client, which can be used to interact with a cluster.
3
+ """
4
+
5
+ import ctypes
6
+ import yaml
7
+
8
+ from skyramp.utils import _library, _call_function
9
+
10
+
11
+ class _Client:
12
+ """
13
+ Skyramp client object which can be used to interact with a cluster.
14
+ """
15
+
16
+ def __init__(
17
+ self,
18
+ kubeconfig_path: str = "",
19
+ cluster_name: str = "",
20
+ context: str = "",
21
+ ) -> None:
22
+ """
23
+ Initializes a Skyramp Client.
24
+
25
+ kubeconfig_path: The filesystem path of a kubeconfig
26
+ cluster_name: The name of the cluster.
27
+ context: The Kubernetes context within a kubeconfig
28
+ """
29
+ self.kubeconfig_path = kubeconfig_path
30
+ self.cluster_name = cluster_name
31
+ self.context = context
32
+
33
+ self._namespace_set = set()
34
+
35
+ def apply_local(self) -> None:
36
+ """
37
+ Creates a local cluster.
38
+ """
39
+ apply_local_function = _library.applyLocalWrapper
40
+ argtypes = []
41
+ restype = ctypes.c_char_p
42
+
43
+ _call_function(apply_local_function, argtypes, restype, [])
44
+
45
+ self.kubeconfig_path = self._get_kubeconfig_path()
46
+ if not self.kubeconfig_path:
47
+ raise Exception("no kubeconfig found")
48
+
49
+ def remove_local(self) -> None:
50
+ """
51
+ Removes a local cluster.
52
+ """
53
+ func = _library.removeLocalWrapper
54
+ argtypes = []
55
+ restype = ctypes.c_char_p
56
+
57
+ _call_function(func, argtypes, restype, [])
58
+
59
+ def add_kubeconfig(
60
+ self,
61
+ context: str,
62
+ cluster_name: str,
63
+ kubeconfig_path: str,
64
+ ) -> None:
65
+ """
66
+ Adds a preexisting Kubeconfig file to Skyramp.
67
+
68
+ context: The kubeconfig context to use
69
+ cluster_name: Name of the cluster
70
+ kubeconfig_path: filepath of the kubeconfig
71
+ """
72
+ func = _library.addKubeconfigWrapper
73
+ argtypes = [ctypes.c_char_p, ctypes.c_bool, ctypes.c_char_p, ctypes.c_char_p]
74
+ restype = ctypes.c_char_p
75
+
76
+ _call_function(
77
+ func,
78
+ argtypes,
79
+ restype,
80
+ [
81
+ context.encode(),
82
+ cluster_name.encode(),
83
+ kubeconfig_path.encode(),
84
+ ],
85
+ )
86
+
87
+ self.kubeconfig_path = kubeconfig_path
88
+
89
+ def remove_cluster(self, cluster_name: str) -> None:
90
+ """
91
+ Removes a cluster, corresponding to the name, from Skyramp
92
+ """
93
+ func = _library.removeClusterFromConfigWrapper
94
+ argtypes = [ctypes.c_char_p]
95
+ restype = ctypes.c_char_p
96
+
97
+ _call_function(func, argtypes, restype, [cluster_name.encode()])
98
+
99
+ def deploy_skyramp_worker(
100
+ self, namespace: str, worker_image: str, local_image: bool
101
+ ) -> None:
102
+ """
103
+ Installs a Skyramp worker onto a cluster if one is registered with Skyramp
104
+ """
105
+ if not self.kubeconfig_path:
106
+ raise Exception("no cluster to deploy worker to")
107
+
108
+ func = _library.deploySkyrampWorkerWrapper
109
+ argtypes = [ctypes.c_char_p, ctypes.c_char_p, ctypes.c_bool]
110
+ restype = ctypes.c_char_p
111
+
112
+ _call_function(
113
+ func,
114
+ argtypes,
115
+ restype,
116
+ [namespace.encode(), worker_image.encode(), local_image],
117
+ )
118
+
119
+ self._namespace_set.add(namespace)
120
+
121
+ def delete_skyramp_worker(self, namespace: str) -> None:
122
+ """
123
+ Removes the Skyramp worker, if a Skyramp worker is installed on a registered Skyramp cluster
124
+ """
125
+ if not self.kubeconfig_path:
126
+ raise Exception("no cluster to delete worker from")
127
+
128
+ if namespace not in self._namespace_set:
129
+ raise Exception(f"no worker to delete from {namespace} namespace")
130
+
131
+ func = _library.deleteSkyrampWorkerWrapper
132
+ argtypes = [ctypes.c_char_p]
133
+ restype = ctypes.c_char_p
134
+
135
+ _call_function(func, argtypes, restype, [namespace.encode()])
136
+
137
+ self._namespace_set.remove(namespace)
138
+
139
+ def mocker_apply(self, namespace: str, endpoint: str) -> None:
140
+ """
141
+ Applies a configuration to mocker.
142
+ namespace: The namespace where mocker resides
143
+ endpoint: The Skyramp enpdoint object
144
+ """
145
+ yaml_string = yaml.dump(endpoint.mock_description)
146
+
147
+ func = _library.applyMockDescriptionWrapper
148
+ argtypes = [
149
+ ctypes.c_char_p,
150
+ ctypes.c_char_p,
151
+ ]
152
+ restype = ctypes.c_char_p # pylint: disable=duplicate-code
153
+
154
+ _call_function(
155
+ func,
156
+ argtypes,
157
+ restype,
158
+ [
159
+ namespace.encode(),
160
+ yaml_string.encode(),
161
+ ],
162
+ )
163
+
164
+ # This will be implemented when scenarios are added.
165
+ # def tester_start(self, namespace: str, scenario: str) -> str:
166
+ # """
167
+ # Runs tester at the given namespace and test name
168
+ # namespace: The namespace where mocker resides
169
+ # """
170
+ # func = _library.runTesterStartWrapper
171
+ # argtypes = [ctypes.c_char_p, ctypes.c_char_p]
172
+ # restype = ctypes.c_char_p
173
+
174
+ # _call_function(
175
+ # func, argtypes, restype, [namespace.encode(), test_description.encode()]
176
+ # )
177
+
178
+ def _get_kubeconfig_path(self) -> str:
179
+ func = _library.getKubeConfigPath
180
+ argtypes = []
181
+ restype = ctypes.c_char_p
182
+
183
+ return _call_function(func, argtypes, restype, [], True)
@@ -0,0 +1,166 @@
1
+ """
2
+ Contains helpers for interacting with Skyramp endpoints.
3
+ """
4
+
5
+ from abc import ABC, abstractmethod
6
+
7
+ import os
8
+ import ctypes
9
+ import json
10
+ import yaml
11
+
12
+ from skyramp.utils import _library, _call_function
13
+
14
+ class _Endpoint(ABC):
15
+ """
16
+ Base class for endpoints. This should not be used for instantiation.
17
+ """
18
+
19
+ @abstractmethod
20
+ def __init__(self, endpoint_data: str) -> None:
21
+ try:
22
+ endpoint = json.loads(endpoint_data)
23
+ self.services = endpoint["services"]
24
+ self.endpoint = endpoint["endpoints"][0]
25
+
26
+ for method in self.endpoint["methods"]:
27
+ method["proxy"] = True
28
+
29
+ self.response_values = endpoint["responseValues"]
30
+
31
+ self.mock_description = {
32
+ "services": self.services,
33
+ "endpoints": [self.endpoint],
34
+ "responseValues": self.response_values
35
+ }
36
+ except:
37
+ raise Exception("failed to parse endpoint data")
38
+
39
+ def mock_method(self, method_name: str, mock_object: str, dynamic: bool = False) -> None:
40
+ """
41
+ Adds the given mock_data blob to the method name for this endpoint.
42
+ """
43
+ for method in self.endpoint["methods"]:
44
+ if method_name not in [method.get("name"), method.get("type")]:
45
+ continue
46
+
47
+ response_name = method["responseValue"]
48
+ method.pop("proxy", None)
49
+
50
+ for response_value in self.response_values:
51
+ if response_value["name"] != response_name:
52
+ continue
53
+
54
+ if dynamic:
55
+ response_value["javascriptPath"] = mock_object
56
+ response_value.pop("blob", None)
57
+ else:
58
+ response_value["blob"] = mock_object["responseValue"]["blob"]
59
+ response_value.pop("javascriptPath", None)
60
+ return
61
+
62
+ raise Exception(f"method {method_name} not found")
63
+
64
+ def mock_method_from_file(self, method_name: str, file_name: str) -> None:
65
+ """
66
+ Uses the given mock data from a provided file, and associates it with the
67
+ corresponding method_name for this endpoint.
68
+ """
69
+ _, file_ext = os.path.splitext(file_name)
70
+
71
+ try:
72
+ with open(file_name) as file:
73
+ file_contents = file.read()
74
+ except:
75
+ raise Exception(f"failed to open file: {file_name}")
76
+
77
+ dynamic = False
78
+
79
+ if file_ext == ".json":
80
+ data = json.loads(file_contents)
81
+ elif file_ext in [".yaml", ".yml"]:
82
+ data = yaml.safe_load(file_contents)
83
+ elif file_ext == ".js":
84
+ dynamic = True
85
+ data = file_name
86
+ else:
87
+ raise Exception(
88
+ f"unsupported file format: {file_ext}. Only JSON and YAML are supported"
89
+ )
90
+
91
+ return self.mock_method(method_name=method_name, mock_object=data, dynamic=dynamic)
92
+
93
+ def write_mock_configuration_to_file(self, alias: str) -> None:
94
+ """
95
+ Persists (as a file to be used by Skyramp) all of the mock configurations
96
+ for this endpoint.
97
+
98
+ alias: The name of the networking alias that will be used to reach this endpoint.
99
+ For example, it can be the Kubernetes service name or the Docker alias name.
100
+ """
101
+ try:
102
+ yaml_content = yaml.dump(self.mock_description)
103
+ except:
104
+ raise Exception("failed to convert mock description to YAML")
105
+
106
+ func = _library.writeMockDescriptionWrapper
107
+ argtypes = [ctypes.c_char_p, ctypes.c_char_p]
108
+ restype = ctypes.c_char_p
109
+
110
+ _call_function(func, argtypes, restype, [yaml_content.encode(), alias.encode()])
111
+
112
+
113
+ class _GrpcEndpoint(_Endpoint):
114
+ """
115
+ Represents an endpoint of a gRPC based service.
116
+ """
117
+
118
+ def __init__(self, name: str, service: str, port: int, pb_file: str) -> None:
119
+ """
120
+ name: Name of the endpoint
121
+ service: The service name to associate with this endpoint
122
+ port: Port number where the endpoint will be reached
123
+ pb_file: Protobuf file with definitions corresponding to this endpoint
124
+ """
125
+ func = _library.newGrpcEndpointWrapper
126
+ argtypes = [ctypes.c_char_p, ctypes.c_char_p, ctypes.c_int, ctypes.c_char_p]
127
+ restype = ctypes.c_char_p
128
+
129
+ output = _call_function(
130
+ func,
131
+ argtypes,
132
+ restype,
133
+ [name.encode(), service.encode(), port, pb_file.encode()],
134
+ True,
135
+ )
136
+
137
+ super().__init__(output)
138
+
139
+
140
+ class _RestEndpoint(_Endpoint):
141
+ """
142
+ Represents an endpoint of a REST based service.
143
+ """
144
+
145
+ def __init__(
146
+ self, name: str, openapi_tag: str, port: int, openapi_file: str
147
+ ) -> None:
148
+ """
149
+ name: name of the endpoint
150
+ openapi_tag: (optional) tag to filter an OpenAPI file
151
+ port: Port number where the endpoint will be reached
152
+ openapi_file: (optional) OpenAPI file with definitions corresponding to this endpoint
153
+ """
154
+ func = _library.newRestEndpointWrapper
155
+ argtypes = [ctypes.c_char_p, ctypes.c_char_p, ctypes.c_int, ctypes.c_char_p]
156
+ restype = ctypes.c_char_p
157
+
158
+ output = _call_function(
159
+ func,
160
+ argtypes,
161
+ restype,
162
+ [name.encode(), openapi_tag.encode(), port, openapi_file.encode()],
163
+ True,
164
+ )
165
+
166
+ super().__init__(output)
@@ -0,0 +1,53 @@
1
+ """
2
+ Contains internal utilities
3
+ """
4
+
5
+ import platform
6
+ import os
7
+ import ctypes
8
+
9
+ def _get_c_library():
10
+ system = platform.system()
11
+ processor = platform.processor()
12
+
13
+ lib_dir = os.path.join(os.path.dirname(__file__), "lib")
14
+ lib_file = ""
15
+
16
+ if system == "Darwin":
17
+ if processor == "arm":
18
+ lib_file = "dev-darwin-arm64.dylib"
19
+
20
+ if lib_file == "":
21
+ raise Exception(
22
+ f"unsupported system and architecture. System: {system}, Architecture: {processor}"
23
+ )
24
+
25
+ lib_path = os.path.join(lib_dir, lib_file)
26
+
27
+ return ctypes.cdll.LoadLibrary(lib_path)
28
+
29
+ def _call_function(func, argtypes, restype, args, return_output=False):
30
+ func.argtypes = argtypes
31
+ func.restype = restype
32
+
33
+ output = func(*args)
34
+ if not output:
35
+ return None
36
+
37
+ output_bytes = ctypes.string_at(output)
38
+ output = output_bytes.decode()
39
+
40
+ if return_output:
41
+ return output
42
+
43
+ # If output is not expected, the result output is parsed as an exception
44
+ if len(output) > 0:
45
+ raise Exception(output)
46
+
47
+ return None
48
+
49
+
50
+ _library = _get_c_library()
51
+
52
+ if _library is None:
53
+ raise Exception("failed to load Skyramp C library")