Skip to content

Commit 5a3fe72

Browse files
Merge pull request #65 from splunk/dev-merge-meta
Feat: implement meta files merging in the workflow
2 parents 2e0966a + 3a408fe commit 5a3fe72

File tree

5 files changed

+103
-6
lines changed

5 files changed

+103
-6
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ Assumptions:
2727
├── es
2828
│ ├── app1
2929
│ │ └── logging.conf
30+
| | └── local.meta
3031
│ └── deployment.yml
3132
└── ses
3233
└── deployment.yml

deploy.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ def main():
4747
# Check if the configuration exists for the app
4848
path = check_all_letter_cases(sys.argv[1], app)
4949
if path:
50-
unpack_merge_conf_and_repack(app, path)
50+
unpack_merge_conf_and_meta_repack(app, path)
5151
else:
5252
print(f"No configuration found for app {app}. Skipping.")
5353

@@ -67,7 +67,7 @@ def main():
6767
):
6868
distribution_status = distribute_app(app, target_url, token)
6969
if distribution_status == 200:
70-
print(f"App {app} successfully distributed.")
70+
print(f"App {app} successfully distributed.\n")
7171
deployment_report[app]["distribution"] = "success"
7272
else:
7373
print(f"App {app} failed distribution.")
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
[manager/accesscontrols]
2+
access = read : [ admin, sc_admin ], write : [ admin, sc_admin ]
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
[manager/accesscontrols]
2+
access = read : [ admin, sc_admin ], write : [ admin, sc_admin ]

utils.py

Lines changed: 96 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
import configparser
1313
import xml.etree.ElementTree as ET
1414

15+
from io import StringIO
16+
1517

1618
SPLUNK_APPINSPECT_BASE_URL = "https://appinspect.splunk.com/v1"
1719
SPLUNKBASE_BASE_URL = "https://splunkbase.splunk.com/api/account:login"
@@ -77,6 +79,35 @@ def download_file_from_s3(bucket_name: str, object_name: str, file_name: str) ->
7779
except Exception as e:
7880
print(f"Error downloading {object_name} from {bucket_name}: {e}")
7981

82+
def preprocess_empty_headers(file_path: str) -> list:
83+
"""
84+
Preprocess the file to handle empty section headers by replacing `[]` with a valid section name.
85+
"""
86+
valid_lines = []
87+
with open(file_path, 'r') as file:
88+
for line in file:
89+
# Replace empty section headers with a placeholder
90+
if line.strip() == "[]":
91+
valid_lines.append("[DEFAULT]\n") # Or any placeholder section name
92+
else:
93+
valid_lines.append(line)
94+
return valid_lines
95+
96+
def replace_default_with_empty_header(file_path: str) -> None:
97+
"""
98+
Replace '[DEFAULT]' header with '[]' in the specified file.
99+
"""
100+
with open(file_path, 'r') as file:
101+
lines = file.readlines()
102+
103+
with open(file_path, 'w') as file:
104+
for line in lines:
105+
# Replace '[DEFAULT]' with '[]'
106+
if line.strip() == "[DEFAULT]":
107+
file.write("[]\n")
108+
else:
109+
file.write(line)
110+
80111
def merge_or_copy_conf(source_path: str, dest_path: str) -> None:
81112
# Get the filename from the source path
82113
filename = os.path.basename(source_path)
@@ -111,7 +142,61 @@ def merge_or_copy_conf(source_path: str, dest_path: str) -> None:
111142
dest_config.write(file)
112143
print(f"Merged configuration saved to {dest_file}")
113144

114-
def unpack_merge_conf_and_repack(app: str, path: str) -> None:
145+
def merge_or_copy_meta(local_meta_file: str, default_dir: str) -> None:
146+
"""Merge local.meta with default.meta"""
147+
filename = os.path.basename(local_meta_file)
148+
dest_file = os.path.join(default_dir, "default.meta")
149+
150+
# Check if the file exists in the destination directory
151+
if not os.path.exists(dest_file):
152+
# If the file doesn't exist, copy it
153+
shutil.copy(local_meta_file, dest_file)
154+
print(f"Copied {filename} to {dest_file}")
155+
else:
156+
# If the file exists, merge the configurations
157+
print(f"Merging {filename} with existing file in {dest_file}")
158+
159+
# Preprocess the default file
160+
default_preprocessed_lines = preprocess_empty_headers(dest_file)
161+
default_preprocessed_content = StringIO(''.join(default_preprocessed_lines))
162+
163+
# Read the default.meta file
164+
default_meta = configparser.ConfigParser()
165+
default_meta.read_file(default_preprocessed_content)
166+
167+
# Preprocess the local file
168+
local_preprocessed_lines = preprocess_empty_headers(local_meta_file)
169+
local_preprocessed_content = StringIO(''.join(local_preprocessed_lines))
170+
171+
# Read the local.meta file
172+
local_meta = configparser.ConfigParser()
173+
local_meta.read_file(local_preprocessed_content)
174+
175+
# Merge local.meta into default.meta
176+
for section in local_meta.sections():
177+
if not default_meta.has_section(section):
178+
default_meta.add_section(section)
179+
for option, value in local_meta.items(section):
180+
if default_meta.has_option(section, option):
181+
# Merge logic: Option exists in both, decide whether to overwrite
182+
default_value = default_meta.get(section, option)
183+
if value != default_value:
184+
print(f"Conflict detected: {section} {option} - {default_value} -> {value}")
185+
# Overwrite the option in default.meta
186+
default_meta.set(section, option, value)
187+
default_meta.set(section, option, value)
188+
189+
# Write the merged configuration back to the output file
190+
with open(dest_file, 'w') as file:
191+
default_meta.write(file)
192+
193+
# Replace '[DEFAULT]' with '[]' in the output file
194+
replace_default_with_empty_header(dest_file)
195+
196+
print(f"Merged metadata saved to {dest_file}")
197+
198+
199+
def unpack_merge_conf_and_meta_repack(app: str, path: str) -> None:
115200
"""Unpack the app, load environment configuration files and repack the app."""
116201
temp_dir = "temp_unpack"
117202
os.makedirs(temp_dir, exist_ok=True)
@@ -120,15 +205,23 @@ def unpack_merge_conf_and_repack(app: str, path: str) -> None:
120205
with tarfile.open(f"{app}.tgz", "r:gz") as tar:
121206
tar.extractall(path=temp_dir)
122207
# Create default directory for unpacked app
123-
default_dir = f"{temp_dir}/{app}/default"
124-
os.makedirs(default_dir, exist_ok=True)
208+
base_default_dir = f"{temp_dir}/{app}"
125209
# Load the environment configuration files
126210
app_dir = path
127211
# Copy all .conf files in app_dir to temp_dir of unpacked app
128212
for file in os.listdir(app_dir):
129213
if file.endswith(".conf"):
214+
default_dir = base_default_dir + "/default"
215+
os.makedirs(default_dir, exist_ok=True)
130216
source_path = os.path.join(app_dir, file)
131217
merge_or_copy_conf(source_path, default_dir)
218+
# Copy all metadata files in app_dir to temp_dir of unpacked app
219+
for file in os.listdir(app_dir):
220+
if file.endswith(".meta"):
221+
default_dir = base_default_dir + "/metadata"
222+
os.makedirs(default_dir, exist_ok=True)
223+
source_path = os.path.join(app_dir, file)
224+
merge_or_copy_meta(source_path, default_dir)
132225
# Repack the app and place it in the root directory
133226
with tarfile.open(f"{app}.tgz", "w:gz") as tar:
134227
for root, _, files in os.walk(f"{temp_dir}/{app}"):
@@ -137,7 +230,6 @@ def unpack_merge_conf_and_repack(app: str, path: str) -> None:
137230
arcname = os.path.relpath(full_path, temp_dir)
138231
tar.add(full_path, arcname=arcname)
139232

140-
141233
def get_appinspect_token() -> str:
142234
"""
143235
Authenticate to the Splunk Cloud.

0 commit comments

Comments
 (0)