forked from envoyproxy/envoy
-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathmigrate.py
273 lines (244 loc) · 11.6 KB
/
migrate.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
# API upgrade business logic.
import copy
import re
from tools.api_proto_plugin import traverse
from tools.api_proto_plugin import visitor
from tools.protoxform import options
from tools.protoxform import utils
from envoy_api_canonical.envoy.annotations import resource_pb2
from udpa.annotations import migrate_pb2
from udpa.annotations import status_pb2
from google.api import annotations_pb2
ENVOY_API_TYPE_REGEX_STR = 'envoy_api_(msg|enum_value|field|enum)_([\w\.]+)'
ENVOY_COMMENT_WITH_TYPE_REGEX = re.compile('<%s>|:ref:`%s`' %
(ENVOY_API_TYPE_REGEX_STR, ENVOY_API_TYPE_REGEX_STR))
class UpgradeVisitor(visitor.Visitor):
"""Visitor to generate an upgraded proto from a FileDescriptor proto.
See visitor.Visitor for visitor method docs comments.
"""
def __init__(self, n, typedb, envoy_internal_shadow, package_version_status):
self._base_version = n
self._typedb = typedb
self._envoy_internal_shadow = envoy_internal_shadow
self._package_version_status = package_version_status
def _UpgradedComment(self, c):
def UpgradeType(match):
# We're upgrading a type within a RST anchor reference here. These are
# stylized and match the output format of tools/protodoc. We need to do
# some special handling of field/enum values, and also the normalization
# that was performed in v2 for envoy.api.v2 types.
label_ref_type, label_normalized_type_name, section_ref_type, section_normalized_type_name = match.groups(
)
if label_ref_type is not None:
ref_type = label_ref_type
normalized_type_name = label_normalized_type_name
else:
ref_type = section_ref_type
normalized_type_name = section_normalized_type_name
if ref_type == 'field' or ref_type == 'enum_value':
normalized_type_name, residual = normalized_type_name.rsplit('.', 1)
else:
residual = ''
type_name = 'envoy.' + normalized_type_name
api_v2_type_name = 'envoy.api.v2.' + normalized_type_name
if type_name in self._typedb.types:
type_desc = self._typedb.types[type_name]
else:
# We need to deal with envoy.api.* normalization in the v2 API. We won't
# need this in v3+, so rather than churn docs, we just have this workaround.
type_desc = self._typedb.types[api_v2_type_name]
repl_type = type_desc.next_version_type_name[
len('envoy.'):] if type_desc.next_version_type_name else normalized_type_name
# TODO(htuch): this should really either go through the type database or
# via the descriptor pool and annotations, but there are only two of these
# we need for the initial v2 -> v3 docs cut, so hard coding for now.
# Tracked at https://github.com/envoyproxy/envoy/issues/9734.
if repl_type == 'config.route.v3.RouteAction':
if residual == 'host_rewrite':
residual = 'host_rewrite_literal'
elif residual == 'auto_host_rewrite_header':
residual = 'auto_host_rewrite'
new_ref = 'envoy_api_%s_%s%s' % (ref_type, repl_type, '.' + residual if residual else '')
if label_ref_type is not None:
return '<%s>' % new_ref
else:
return ':ref:`%s`' % new_ref
return re.sub(ENVOY_COMMENT_WITH_TYPE_REGEX, UpgradeType, c)
def _UpgradedPostMethod(self, m):
return re.sub(r'^/v%d/' % self._base_version, '/v%d/' % (self._base_version + 1), m)
# Upgraded type using canonical type naming, e.g. foo.bar.
def _UpgradedTypeCanonical(self, t):
if not t.startswith('envoy'):
return t
type_desc = self._typedb.types[t]
if type_desc.next_version_type_name:
return type_desc.next_version_type_name
return t
# Upgraded type using internal type naming, e.g. .foo.bar.
def _UpgradedType(self, t):
if not t.startswith('.envoy'):
return t
return '.' + self._UpgradedTypeCanonical(t[1:])
def _Deprecate(self, proto, field_or_value):
"""Deprecate a field or value in a message/enum proto.
Args:
proto: DescriptorProto or EnumDescriptorProto message.
field_or_value: field or value inside proto.
"""
if self._envoy_internal_shadow:
field_or_value.name = 'hidden_envoy_deprecated_' + field_or_value.name
else:
reserved = proto.reserved_range.add()
reserved.start = field_or_value.number
reserved.end = field_or_value.number + 1
proto.reserved_name.append(field_or_value.name)
options.AddHideOption(field_or_value.options)
def _Rename(self, proto, migrate_annotation):
"""Rename a field/enum/service/message
Args:
proto: DescriptorProto or corresponding proto message
migrate_annotation: udpa.annotations.MigrateAnnotation message
"""
if migrate_annotation.rename:
proto.name = migrate_annotation.rename
migrate_annotation.rename = ""
def _OneofPromotion(self, msg_proto, field_proto, migrate_annotation):
"""Promote a field to a oneof.
Args:
msg_proto: DescriptorProto for message containing field.
field_proto: FieldDescriptorProto for field.
migrate_annotation: udpa.annotations.FieldMigrateAnnotation message
"""
if migrate_annotation.oneof_promotion:
oneof_index = -1
for n, oneof_decl in enumerate(msg_proto.oneof_decl):
if oneof_decl.name == migrate_annotation.oneof_promotion:
oneof_index = n
if oneof_index == -1:
oneof_index = len(msg_proto.oneof_decl)
oneof_decl = msg_proto.oneof_decl.add()
oneof_decl.name = migrate_annotation.oneof_promotion
field_proto.oneof_index = oneof_index
migrate_annotation.oneof_promotion = ""
def VisitService(self, service_proto, type_context):
upgraded_proto = copy.deepcopy(service_proto)
for m in upgraded_proto.method:
if m.options.HasExtension(annotations_pb2.http):
http_options = m.options.Extensions[annotations_pb2.http]
# TODO(htuch): figure out a more systematic approach using the type DB
# to service upgrade.
http_options.post = self._UpgradedPostMethod(http_options.post)
m.input_type = self._UpgradedType(m.input_type)
m.output_type = self._UpgradedType(m.output_type)
if service_proto.options.HasExtension(resource_pb2.resource):
upgraded_proto.options.Extensions[resource_pb2.resource].type = self._UpgradedTypeCanonical(
service_proto.options.Extensions[resource_pb2.resource].type)
return upgraded_proto
def VisitMessage(self, msg_proto, type_context, nested_msgs, nested_enums):
upgraded_proto = copy.deepcopy(msg_proto)
if upgraded_proto.options.deprecated and not self._envoy_internal_shadow:
options.AddHideOption(upgraded_proto.options)
options.SetVersioningAnnotation(upgraded_proto.options, type_context.name)
# Mark deprecated fields as ready for deletion by protoxform.
for f in upgraded_proto.field:
if f.options.deprecated:
self._Deprecate(upgraded_proto, f)
if self._envoy_internal_shadow:
# When shadowing, we use the upgraded version of types (which should
# themselves also be shadowed), to allow us to avoid unnecessary
# references to the previous version (and complexities around
# upgrading during API boosting).
f.type_name = self._UpgradedType(f.type_name)
else:
# Make sure the type name is erased so it isn't picked up by protoxform
# when computing deps.
f.type_name = ""
else:
f.type_name = self._UpgradedType(f.type_name)
if f.options.HasExtension(migrate_pb2.field_migrate):
field_migrate = f.options.Extensions[migrate_pb2.field_migrate]
self._Rename(f, field_migrate)
self._OneofPromotion(upgraded_proto, f, field_migrate)
# Upgrade nested messages.
del upgraded_proto.nested_type[:]
upgraded_proto.nested_type.extend(nested_msgs)
# Upgrade enums.
del upgraded_proto.enum_type[:]
upgraded_proto.enum_type.extend(nested_enums)
return upgraded_proto
def VisitEnum(self, enum_proto, type_context):
upgraded_proto = copy.deepcopy(enum_proto)
if upgraded_proto.options.deprecated and not self._envoy_internal_shadow:
options.AddHideOption(upgraded_proto.options)
for v in upgraded_proto.value:
if v.options.deprecated:
# We need special handling for the zero field, as proto3 needs some value
# here.
if v.number == 0 and not self._envoy_internal_shadow:
v.name = 'DEPRECATED_AND_UNAVAILABLE_DO_NOT_USE'
else:
# Mark deprecated enum values as ready for deletion by protoxform.
self._Deprecate(upgraded_proto, v)
elif v.options.HasExtension(migrate_pb2.enum_value_migrate):
self._Rename(v, v.options.Extensions[migrate_pb2.enum_value_migrate])
return upgraded_proto
def VisitFile(self, file_proto, type_context, services, msgs, enums):
upgraded_proto = copy.deepcopy(file_proto)
# Upgrade imports.
upgraded_proto.dependency[:] = [
dependency for dependency in upgraded_proto.dependency
if dependency not in ("udpa/annotations/migrate.proto")
]
# Upgrade package.
upgraded_proto.package = self._typedb.next_version_protos[upgraded_proto.name].qualified_package
upgraded_proto.name = self._typedb.next_version_protos[upgraded_proto.name].proto_path
upgraded_proto.options.ClearExtension(migrate_pb2.file_migrate)
upgraded_proto.options.Extensions[
status_pb2.file_status].package_version_status = self._package_version_status
# Upgrade comments.
for location in upgraded_proto.source_code_info.location:
location.leading_comments = self._UpgradedComment(location.leading_comments)
location.trailing_comments = self._UpgradedComment(location.trailing_comments)
for n, c in enumerate(location.leading_detached_comments):
location.leading_detached_comments[n] = self._UpgradedComment(c)
# Upgrade services.
del upgraded_proto.service[:]
upgraded_proto.service.extend(services)
# Upgrade messages.
del upgraded_proto.message_type[:]
upgraded_proto.message_type.extend(msgs)
# Upgrade enums.
del upgraded_proto.enum_type[:]
upgraded_proto.enum_type.extend(enums)
return upgraded_proto
def VersionUpgradeXform(n, envoy_internal_shadow, file_proto, params):
"""Transform a FileDescriptorProto from vN[alpha\d] to v(N+1).
Args:
n: version N to upgrade from.
envoy_internal_shadow: generate a shadow for Envoy internal use containing deprecated fields.
file_proto: vN[alpha\d] FileDescriptorProto message.
params: plugin parameters.
Returns:
v(N+1) FileDescriptorProto message.
"""
# Load type database.
if params['type_db_path']:
utils.LoadTypeDb(params['type_db_path'])
typedb = utils.GetTypeDb()
# If this isn't a proto in an upgraded package, return None.
if file_proto.name not in typedb.next_version_protos or not typedb.next_version_protos[
file_proto.name]:
return None
# Otherwise, this .proto needs upgrading, do it.
freeze = 'extra_args' in params and params['extra_args'] == 'freeze'
existing_pkg_version_status = file_proto.options.Extensions[
status_pb2.file_status].package_version_status
# Normally, we are generating the NEXT_MAJOR_VERSION_CANDIDATE. However, if
# freezing and previously this was the active major version, the migrated
# version is now the ACTIVE version.
if freeze and existing_pkg_version_status == status_pb2.ACTIVE:
package_version_status = status_pb2.ACTIVE
else:
package_version_status = status_pb2.NEXT_MAJOR_VERSION_CANDIDATE
return traverse.TraverseFile(
file_proto, UpgradeVisitor(n, typedb, envoy_internal_shadow, package_version_status))