Skip to content

Commit dd62474

Browse files
committed
Yaml lists with kubectl-like behavior
1 parent 4d4ce21 commit dd62474

File tree

4 files changed

+184
-35
lines changed

4 files changed

+184
-35
lines changed

kubernetes/e2e_test/test_utils.py

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,4 +113,43 @@ def test_api_service(self):
113113
name="v1alpha1.wardle.k8s.io")
114114
self.assertIsNotNone(svc)
115115
resp = k8s_api.delete_api_service(
116-
name="v1alpha1.wardle.k8s.io", body={})
116+
name="v1alpha1.wardle.k8s.io", body={})
117+
118+
def test_list(self):
119+
k8s_client = client.api_client.ApiClient(configuration=self.config)
120+
k8s_api = utils.create_from_yaml(k8s_client,
121+
"kubernetes/e2e_test/test_yaml/list.yaml")
122+
svc_api = k8s_api[0]
123+
self.assertEqual("v1", svc_api.get_api_resources().group_version)
124+
svc = svc_api.read_namespaced_service(name="list-service-test",
125+
namespace="default")
126+
self.assertIsNotNone(svc)
127+
ext_api = k8s_api[1]
128+
self.assertEqual("extensions/v1beta1",
129+
ext_api.get_api_resources().group_version)
130+
dep = ext_api.read_namespaced_deployment(name="list-deployment-test",
131+
namespace="default")
132+
self.assertIsNotNone(dep)
133+
ext_api.delete_namespaced_deployment(name="list-deployment-test",
134+
namespace="default", body={})
135+
svc_api.delete_namespaced_service(name="list-service-test",
136+
namespace="default", body={})
137+
138+
def test_multi_resource(self):
139+
k8s_client = client.api_client.ApiClient(configuration=self.config)
140+
k8s_api = utils.create_from_yaml(k8s_client,
141+
"kubernetes/e2e_test/test_yaml/multi-resource-yaml.yaml")
142+
svc_api = k8s_api[0]
143+
self.assertEqual("v1", svc_api.get_api_resources().group_version)
144+
svc = svc_api.read_namespaced_service(name="mock",
145+
namespace="default")
146+
self.assertIsNotNone(svc)
147+
ctr_api = k8s_api[1]
148+
self.assertEqual("v1", ctr_api.get_api_resources().group_version)
149+
ctr = ctr_api.read_namespaced_replication_controller(
150+
name="mock", namespace="default")
151+
self.assertIsNotNone(ctr)
152+
ctr_api.delete_namespaced_replication_controller(name="mock",
153+
namespace="default", body={})
154+
svc_api.delete_namespaced_service(name="mock",
155+
namespace="default", body={})
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
apiVersion: v1
2+
kind: List
3+
items:
4+
- apiVersion: v1
5+
kind: Service
6+
metadata:
7+
name: list-service-test
8+
spec:
9+
ports:
10+
- protocol: TCP
11+
port: 80
12+
selector:
13+
app: list-deployment-test
14+
- apiVersion: extensions/v1beta1
15+
kind: Deployment
16+
metadata:
17+
name: list-deployment-test
18+
labels:
19+
app: list-deployment-test
20+
spec:
21+
replicas: 1
22+
template:
23+
metadata:
24+
labels:
25+
app: list-deployment-test
26+
spec:
27+
containers:
28+
- name: nginx
29+
image: nginx
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
apiVersion: v1
2+
kind: Service
3+
metadata:
4+
name: mock
5+
labels:
6+
app: mock
7+
spec:
8+
ports:
9+
- port: 99
10+
protocol: TCP
11+
targetPort: 9949
12+
selector:
13+
app: mock
14+
---
15+
apiVersion: v1
16+
kind: ReplicationController
17+
metadata:
18+
name: mock
19+
spec:
20+
replicas: 1
21+
selector:
22+
app: mock
23+
template:
24+
metadata:
25+
labels:
26+
app: mock
27+
spec:
28+
containers:
29+
- name: mock-container
30+
image: k8s.gcr.io/pause:2.0
31+
ports:
32+
- containerPort: 9949
33+
protocol: TCP

kubernetes/utils/create_from_yaml.py

Lines changed: 82 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -23,51 +23,99 @@
2323
from kubernetes import client
2424

2525

26-
def create_from_yaml(k8s_client, yaml_file, verbose=False, **kwargs):
26+
def create_from_yaml(
27+
k8s_client,
28+
yaml_file,
29+
verbose=False,
30+
output_list=False,
31+
**kwargs):
2732
"""
2833
Perform an action from a yaml file. Pass True for verbose to
2934
print confirmation information.
3035
Input:
3136
yaml_file: string. Contains the path to yaml file.
3237
k8s_cline: an ApiClient object, initialized with the client args.
38+
verbose: If True, print confirmation from the create action. Default is False.
39+
output_list: compatibility option with v8.0.0. Default is False.
40+
Function returns a single api object when there is only one when set False.
41+
Does not affect when multiple objects are generated.
3342
34-
Available parameters for performing the subsequent action:
43+
Returns:
44+
An k8s api object or list of apis objects created from YAML.
45+
When a single object is generated, return type is dependent on output_list.
46+
47+
Available parameters for creating <kind>:
3548
:param async_req bool
3649
:param bool include_uninitialized: If true, partially initialized resources are included in the response.
3750
:param str pretty: If 'true', then the output is pretty printed.
3851
:param str dry_run: When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed
3952
"""
4053

54+
k8s_api_all = []
4155
with open(path.abspath(yaml_file)) as f:
42-
yml_object = yaml.load(f)
43-
# TODO: case of yaml file containing multiple objects
44-
group, _, version = yml_object["apiVersion"].partition("/")
45-
if version == "":
46-
version = group
47-
group = "core"
48-
# Take care for the case e.g. api_type is "apiextensions.k8s.io"
49-
# Only replace the last instance
50-
group = "".join(group.rsplit(".k8s.io", 1))
51-
fcn_to_call = "{0}{1}Api".format(group.capitalize(),
52-
version.capitalize())
53-
k8s_api = getattr(client, fcn_to_call)(k8s_client)
54-
# Replace CamelCased action_type into snake_case
55-
kind = yml_object["kind"]
56-
kind = re.sub('(.)([A-Z][a-z]+)', r'\1_\2', kind)
57-
kind = re.sub('([a-z0-9])([A-Z])', r'\1_\2', kind).lower()
58-
# Decide which namespace we are going to put the object in,
59-
# if any
60-
if "namespace" in yml_object["metadata"]:
61-
namespace = yml_object["metadata"]["namespace"]
62-
else:
63-
namespace = "default"
64-
# Expect the user to create namespaced objects more often
65-
if hasattr(k8s_api, "create_namespaced_{0}".format(kind)):
66-
resp = getattr(k8s_api, "create_namespaced_{0}".format(kind))(
67-
body=yml_object, namespace=namespace, **kwargs)
68-
else:
69-
resp = getattr(k8s_api, "create_{0}".format(kind))(
70-
body=yml_object, **kwargs)
71-
if verbose:
72-
print("{0} created. status='{1}'".format(kind, str(resp.status)))
73-
return k8s_api
56+
yml_document_all = yaml.load_all(f)
57+
# Load all documents from a single YAML file
58+
for yml_document in yml_document_all:
59+
# If it is a list type, will need to iterate its items
60+
if "List" in yml_document["kind"]:
61+
# Could be "List" or "Pod/Service/...List"
62+
# This is a list type. iterate within its items
63+
for yml_object in yml_document["items"]:
64+
try:
65+
k8s_api_all.append(create_from_yaml_single_item(
66+
k8s_client, yml_object, verbose, **kwargs))
67+
except client.rest.ApiException as api_exception:
68+
print(
69+
"Error when creating {0}/{1}".format(
70+
yml_document["kind"],
71+
yml_document["metadata"]["name"]),
72+
api_exception)
73+
else:
74+
# This is a single object. Call the single item method
75+
try:
76+
k8s_api_all.append(create_from_yaml_single_item(
77+
k8s_client, yml_document, verbose, **kwargs))
78+
except client.rest.ApiException as api_exception:
79+
print(
80+
"Error when creating {0}/{1}".format(
81+
yml_document["kind"],
82+
yml_document["metadata"]["name"]),
83+
api_exception)
84+
if output_list is False:
85+
if len(k8s_api_all) == 1:
86+
return k8s_api_all[0]
87+
return k8s_api_all
88+
89+
90+
def create_from_yaml_single_item(
91+
k8s_client, yml_object, verbose=False, **kwargs):
92+
group, _, version = yml_object["apiVersion"].partition("/")
93+
if version == "":
94+
version = group
95+
group = "core"
96+
# Take care for the case e.g. api_type is "apiextensions.k8s.io"
97+
# Only replace the last instance
98+
group = "".join(group.rsplit(".k8s.io", 1))
99+
fcn_to_call = "{0}{1}Api".format(group.capitalize(),
100+
version.capitalize())
101+
k8s_api = getattr(client, fcn_to_call)(k8s_client)
102+
# Replace CamelCased action_type into snake_case
103+
kind = yml_object["kind"]
104+
kind = re.sub('(.)([A-Z][a-z]+)', r'\1_\2', kind)
105+
kind = re.sub('([a-z0-9])([A-Z])', r'\1_\2', kind).lower()
106+
# Decide which namespace we are going to put the object in,
107+
# if any
108+
if "namespace" in yml_object["metadata"]:
109+
namespace = yml_object["metadata"]["namespace"]
110+
else:
111+
namespace = "default"
112+
# Expect the user to create namespaced objects more often
113+
if hasattr(k8s_api, "create_namespaced_{0}".format(kind)):
114+
resp = getattr(k8s_api, "create_namespaced_{0}".format(kind))(
115+
body=yml_object, namespace=namespace, **kwargs)
116+
else:
117+
resp = getattr(k8s_api, "create_{0}".format(kind))(
118+
body=yml_object, **kwargs)
119+
if verbose:
120+
print("{0} created. status='{1}'".format(kind, str(resp.status)))
121+
return k8s_api

0 commit comments

Comments
 (0)