Skip to content

Commit b0751ad

Browse files
Michael Linfacebook-github-bot
authored andcommitted
Add PP and OPE Category to FbCode Loggers with OPE Universe
Summary: Creates a codemod for adding PPF and OPE Category (RD) annotations to FbCode loggers that already have a OPE universe annotation. Differential Revision: D79380570 fbshipit-source-id: ca890cb86d22c0d8567fd0e59b1fe5c932b1fdb8
1 parent 5dab061 commit b0751ad

File tree

2 files changed

+355
-0
lines changed

2 files changed

+355
-0
lines changed
Lines changed: 222 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,222 @@
1+
/*
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
#include <thrift/compiler/ast/t_program_bundle.h>
18+
#include <thrift/compiler/codemod/codemod.h>
19+
#include <thrift/compiler/codemod/file_manager.h>
20+
21+
using apache::thrift::compiler::source_manager;
22+
using apache::thrift::compiler::t_program;
23+
using apache::thrift::compiler::t_program_bundle;
24+
using apache::thrift::compiler::codemod::file_manager;
25+
26+
namespace {
27+
28+
class add_operational_annotations {
29+
public:
30+
add_operational_annotations(source_manager& sm, t_program& program)
31+
: fm_(sm, program), prog_(program) {}
32+
33+
void add_includes() {
34+
// Get the file content
35+
std::string_view content = fm_.old_content();
36+
37+
// Check if the include statements already exist
38+
bool zone_policy_include_exists =
39+
content.find(
40+
"include \"dsi/logger/logger_config_fbcode/zone_policy.thrift\"") !=
41+
std::string::npos;
42+
bool operational_data_include_exists =
43+
content.find(
44+
"include \"configerator/structs/capella/types/annotation_types/operational_data/operational_data_annotation.thrift\"") !=
45+
std::string::npos;
46+
47+
// Only add the includes if they don't already exist
48+
if (!zone_policy_include_exists || !operational_data_include_exists) {
49+
// Simply add at position 0 (beginning of file)
50+
std::string includes_to_add;
51+
if (!zone_policy_include_exists) {
52+
includes_to_add +=
53+
"include \"dsi/logger/logger_config_fbcode/zone_policy.thrift\"\n";
54+
}
55+
if (!operational_data_include_exists) {
56+
includes_to_add +=
57+
"include \"configerator/structs/capella/types/annotation_types/operational_data/operational_data_annotation.thrift\"\n";
58+
}
59+
60+
// Check if the next line after our includes contains an include statement
61+
size_t first_line_end = content.find('\n');
62+
if (first_line_end != std::string::npos) {
63+
// Look at the content after the first line
64+
std::string_view next_content = content.substr(first_line_end + 1);
65+
// Check if the next content contains an include statement
66+
bool next_line_has_include =
67+
next_content.find("include ") != std::string::npos;
68+
69+
// Add a blank line after the includes only if the next line doesn't
70+
// have an include
71+
if (!includes_to_add.empty()) {
72+
if (!next_line_has_include) {
73+
includes_to_add += "\n";
74+
}
75+
fm_.add({0, 0, includes_to_add});
76+
}
77+
} else {
78+
// If there's no newline (single line file), just add the includes
79+
if (!includes_to_add.empty()) {
80+
includes_to_add += "\n";
81+
fm_.add({0, 0, includes_to_add});
82+
}
83+
}
84+
}
85+
}
86+
/*
87+
This function checks for the universe OPE annotation.
88+
*/
89+
bool has_operational_annotation() {
90+
std::string_view content = fm_.old_content();
91+
return content.find(target_string) != std::string::npos;
92+
}
93+
94+
/*
95+
This function checks for the if the PPF and OPE category annotations already
96+
exist.
97+
*/
98+
std::pair<bool, bool> check_existing_annotations(
99+
std::string_view next_content) {
100+
bool zone_policy_exists =
101+
next_content.find(zone_policy_string) != std::string::npos;
102+
bool operational_data_exists =
103+
next_content.find(ope_category_string) != std::string::npos;
104+
return {zone_policy_exists, operational_data_exists};
105+
}
106+
107+
/*
108+
This function adds the PPF and OPE category annotations if one doesn't exist.
109+
*/
110+
void add_annotations_at(
111+
size_t line_end,
112+
const std::string& indentation,
113+
bool zone_policy_exists,
114+
bool operational_data_exists) {
115+
std::string annotations_to_add;
116+
if (!zone_policy_exists) {
117+
annotations_to_add += "\n" + indentation + zone_policy_string + "{\n" +
118+
indentation +
119+
" name = purpose_policy_names.TPurposePolicyName.DEFAULT_PURPOSES_OPERATIONAL,\n" +
120+
indentation +
121+
" cipp_enforcement_mode = data_access_policy_metadata.CIPPEnforcementMode.NONE,\n" +
122+
indentation + "}";
123+
}
124+
if (!operational_data_exists) {
125+
annotations_to_add += "\n" + indentation + ope_category_string + "{\n" +
126+
indentation +
127+
" category = operational_data_annotation.Category.RESTRICTED_DEFAULT,\n" +
128+
indentation + "}";
129+
}
130+
if (!annotations_to_add.empty()) {
131+
fm_.add({line_end, line_end, annotations_to_add});
132+
}
133+
}
134+
135+
/*
136+
Calculates identations required for PPF and OPE category annotations.
137+
*/
138+
std::string get_indentation(std::string_view content, size_t pos) {
139+
// Get the indentation of the current line
140+
size_t line_start = content.rfind('\n', pos);
141+
if (line_start == std::string::npos) {
142+
line_start = 0;
143+
} else {
144+
line_start++; // Move past the newline character
145+
}
146+
147+
// Calculate the indentation
148+
std::string indentation;
149+
for (size_t i = line_start;
150+
i < pos && (content[i] == ' ' || content[i] == '\t');
151+
++i) {
152+
indentation += content[i];
153+
}
154+
return indentation;
155+
}
156+
157+
void visit_program() {
158+
// Check if the file has operational annotations
159+
if (has_operational_annotation()) {
160+
// Add required includes for PPF and OPE cateogry annotations.
161+
add_includes();
162+
163+
// Get the file content
164+
std::string_view content = fm_.old_content();
165+
166+
// Find all occurrences of the target string
167+
size_t pos = 0;
168+
while ((pos = content.find(target_string, pos)) != std::string::npos) {
169+
// Find the end of the line containing the target string
170+
size_t line_end = content.find('\n', pos);
171+
if (line_end == std::string::npos) {
172+
line_end = content.size();
173+
}
174+
175+
// Get the indentation
176+
std::string indentation = get_indentation(content, pos);
177+
178+
// Check if the PPF and OPE annotations already exist after this
179+
// occurrence
180+
size_t next_pos = line_end + 1;
181+
std::string_view next_content =
182+
content.substr(next_pos, 1000); // Look ahead a reasonable amount
183+
184+
auto [zone_policy_exists, operational_data_exists] =
185+
check_existing_annotations(next_content);
186+
187+
// Add annotations if they don't exist
188+
if (!zone_policy_exists || !operational_data_exists) {
189+
add_annotations_at(
190+
line_end,
191+
indentation,
192+
zone_policy_exists,
193+
operational_data_exists);
194+
}
195+
196+
// Move past this occurrence
197+
pos = line_end;
198+
}
199+
}
200+
}
201+
202+
void run() {
203+
visit_program();
204+
fm_.apply_replacements();
205+
}
206+
207+
private:
208+
file_manager fm_;
209+
const t_program& prog_;
210+
const std::string target_string =
211+
"@universe.Universe{id = universe.UniverseIdentifier.OPERATIONAL}";
212+
const std::string zone_policy_string = "@zone_policy.PurposePolicy";
213+
const std::string ope_category_string = "@operational_data_annotation.Logger";
214+
};
215+
} // namespace
216+
217+
int main(int argc, char** argv) {
218+
return apache::thrift::compiler::run_codemod(
219+
argc, argv, [](source_manager& sm, t_program_bundle& pb) {
220+
add_operational_annotations(sm, *pb.root_program()).run();
221+
});
222+
}
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
# Copyright (c) Meta Platforms, Inc. and affiliates.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
# // Temp test command: buck2 test //xplat/thrift/compiler/codemod:add_operational_annotations_test
15+
# pyre-unsafe
16+
17+
import os
18+
import shutil
19+
import tempfile
20+
import textwrap
21+
import unittest
22+
23+
import pkg_resources
24+
25+
from xplat.thrift.compiler.codemod.test_utils import read_file, run_binary, write_file
26+
27+
28+
class AddOperationalAnnotationsTest(unittest.TestCase):
29+
def setUp(self):
30+
tmp = tempfile.mkdtemp()
31+
self.addCleanup(shutil.rmtree, tmp, True)
32+
self.tmp = tmp
33+
self.addCleanup(os.chdir, os.getcwd())
34+
os.chdir(self.tmp)
35+
self.maxDiff = None
36+
37+
def trim(self, s):
38+
# Strip whitespace from each line and remove trailing newlines.
39+
return "\n".join([line.strip() for line in s.splitlines()]).rstrip("\n")
40+
41+
def write_and_test(self, file, content, expected_results):
42+
write_file(file, textwrap.dedent(content))
43+
44+
binary = pkg_resources.resource_filename(__name__, "codemod")
45+
run_binary(binary, file)
46+
actual_results = read_file(file)
47+
48+
self.assertEqual(
49+
self.trim(actual_results),
50+
self.trim(expected_results),
51+
)
52+
53+
def test_no_operational_annotation(self):
54+
# When no operational univerese annotation is present, the codemod should not modify the file.
55+
self.write_and_test(
56+
"no_annotation.thrift",
57+
"""\
58+
struct Foo {
59+
1: string name;
60+
}
61+
""",
62+
"""\
63+
struct Foo {
64+
1: string name;
65+
}
66+
""",
67+
)
68+
69+
def test_adding_operational_annotation(self):
70+
# Test case to add include statements and PPF/OPE category annotations.
71+
self.write_and_test(
72+
"single_annotation.thrift",
73+
"""\
74+
@universe.Universe{id = universe.UniverseIdentifier.OPERATIONAL}
75+
struct Foo {
76+
1: string name;
77+
}
78+
""",
79+
"""\
80+
include "dsi/logger/logger_config_fbcode/zone_policy.thrift"
81+
include "configerator/structs/capella/types/annotation_types/operational_data/operational_data_annotation.thrift"
82+
83+
@universe.Universe{id = universe.UniverseIdentifier.OPERATIONAL}
84+
@zone_policy.PurposePolicy{
85+
name = purpose_policy_names.TPurposePolicyName.DEFAULT_PURPOSES_OPERATIONAL,
86+
cipp_enforcement_mode = data_access_policy_metadata.CIPPEnforcementMode.NONE,
87+
}
88+
@operational_data_annotation.Logger{
89+
category = operational_data_annotation.Category.RESTRICTED_DEFAULT,
90+
}
91+
struct Foo {
92+
1: string name;
93+
}
94+
""",
95+
)
96+
97+
def test_already_has_operational_annotation(self):
98+
# Test case to not add OPE annotations if they already exist.
99+
self.write_and_test(
100+
"single_annotation.thrift",
101+
"""\
102+
include "dsi/logger/logger_config_fbcode/zone_policy.thrift"
103+
include "configerator/structs/capella/types/annotation_types/operational_data/operational_data_annotation.thrift"
104+
105+
@universe.Universe{id = universe.UniverseIdentifier.OPERATIONAL}
106+
@zone_policy.PurposePolicy{
107+
name = purpose_policy_names.TPurposePolicyName.DEFAULT_PURPOSES_OPERATIONAL,
108+
cipp_enforcement_mode = data_access_policy_metadata.CIPPEnforcementMode.NONE,
109+
}
110+
@operational_data_annotation.Logger{
111+
category = operational_data_annotation.Category.RESTRICTED_DEFAULT,
112+
}
113+
struct Foo {
114+
1: string name;
115+
}
116+
""",
117+
"""\
118+
include "dsi/logger/logger_config_fbcode/zone_policy.thrift"
119+
include "configerator/structs/capella/types/annotation_types/operational_data/operational_data_annotation.thrift"
120+
121+
@universe.Universe{id = universe.UniverseIdentifier.OPERATIONAL}
122+
@zone_policy.PurposePolicy{
123+
name = purpose_policy_names.TPurposePolicyName.DEFAULT_PURPOSES_OPERATIONAL,
124+
cipp_enforcement_mode = data_access_policy_metadata.CIPPEnforcementMode.NONE,
125+
}
126+
@operational_data_annotation.Logger{
127+
category = operational_data_annotation.Category.RESTRICTED_DEFAULT,
128+
}
129+
struct Foo {
130+
1: string name;
131+
}
132+
""",
133+
)

0 commit comments

Comments
 (0)