Skip to content

Commit f34ce97

Browse files
committed
feat: Add service-specific endpoints support to FSM config parser
Add support for parsing [services] sections with service-specific endpoint_url configuration following AWS SDKs & Tools specification. Changes: - Extend Profile class with endpoint_url and servicesEndpointUrl map - Add FSM states for [services] and [services serviceId] sections - Add GetServiceEndpointUrl() and GetGlobalEndpointUrl() API methods - Support case-insensitive service ID lookup - Add comprehensive test coverage Supports config format: [profile default] endpoint_url = https://global.example.com [services s3] endpoint_url = http://localhost:9000 Add Profile computed getters using Aws::Crt::Optional for proper null handling: - GetServicesName() returns services definition name or empty Optional - GetEndpointUrl() returns global endpoint URL or empty Optional - Add static Profile::GetServiceEndpointUrl() helper for endpoint resolution - Takes profile, services map, and serviceId as parameters - Maintains Profile as stateless data container - Add AWSConfigFileProfileConfigLoader::GetServices() const accessor Uses Aws::Crt::Optional instead of empty strings to properly represent "no value" state. Static helper pattern keeps Profile ABI-stable while enabling service endpoint resolution without coupling to loader internals. added a test for multiple services definition updated test to use .find before creatng the profile update to use a service object instead of using getvalue added 3 more tests verifying duplication urls, modify the services object with more encapsulation
1 parent 9f07797 commit f34ce97

File tree

3 files changed

+515
-6
lines changed

3 files changed

+515
-6
lines changed

src/aws-cpp-sdk-core/include/aws/core/config/AWSProfileConfig.h

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
#include <aws/core/utils/memory/stl/AWSString.h>
99
#include <aws/core/utils/memory/stl/AWSMap.h>
1010
#include <aws/core/auth/AWSCredentials.h>
11+
#include <aws/crt/Optional.h>
1112

1213
namespace Aws
1314
{
@@ -19,6 +20,24 @@ namespace Aws
1920
class Profile
2021
{
2122
public:
23+
/*
24+
* Data container for service endpoints.
25+
*/
26+
class Services
27+
{
28+
public:
29+
Services() = default;
30+
Services(Aws::Map<Aws::String, Aws::String>&& endpoints, Aws::String name)
31+
: m_endpoints(std::move(endpoints)), m_name(std::move(name)) {}
32+
33+
inline Aws::Map<Aws::String, Aws::String> GetEndpoints() const { return m_endpoints; }
34+
inline Aws::String GetServiceBlockName() const { return m_name; }
35+
inline bool IsSet() const { return !m_name.empty(); }
36+
private:
37+
Aws::Map<Aws::String, Aws::String> m_endpoints;
38+
Aws::String m_name;
39+
};
40+
2241
/*
2342
* Data container for a sso-session config entry.
2443
* This is independent of the general profile configuration and used by a bearer auth token provider.
@@ -84,6 +103,10 @@ namespace Aws
84103
inline void SetSourceProfile(const Aws::String& value ) { m_sourceProfile = value; }
85104
inline const Aws::String& GetCredentialProcess() const { return m_credentialProcess; }
86105
inline void SetCredentialProcess(const Aws::String& value ) { m_credentialProcess = value; }
106+
inline const Aws::String& GetGlobalEndpointUrl() const { return m_endpointUrl; }
107+
inline void SetGlobalEndpointUrl(const Aws::String& value) { m_endpointUrl = value; }
108+
inline Services GetServices() const { return m_services; }
109+
inline void SetServices(Services&& services) { m_services = std::move(services); }
87110
inline void SetAllKeyValPairs(const Aws::Map<Aws::String, Aws::String>& map) { m_allKeyValPairs = map; }
88111
inline void SetAllKeyValPairs(Aws::Map<Aws::String, Aws::String>&& map) { m_allKeyValPairs = std::move(map); }
89112
inline const Aws::String GetValue(const Aws::String& key) const
@@ -111,7 +134,9 @@ namespace Aws
111134
Aws::String m_ssoAccountId;
112135
Aws::String m_ssoRoleName;
113136
Aws::String m_defaultsMode;
137+
Aws::String m_endpointUrl;
114138
Aws::Map<Aws::String, Aws::String> m_allKeyValPairs;
139+
Services m_services;
115140

116141
bool m_ssoSessionSet = false;
117142
SsoSession m_ssoSession;

src/aws-cpp-sdk-core/source/config/AWSConfigFileProfileConfigLoader.cpp

Lines changed: 92 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,9 @@ namespace Aws
4040
static const char PROFILE_SECTION[] = "profile";
4141
static const char DEFAULT[] = "default";
4242
static const char SSO_SESSION_SECTION[] = "sso-session";
43+
static const char SERVICES_SECTION[] = "services";
44+
static const char ENDPOINT_URL_KEY[] = "endpoint_url";
45+
static const char IGNORE_CONFIGURED_ENDPOINT_URLS_KEY[] = "ignore_configured_endpoint_urls";
4346
static const char DEFAULTS_MODE_KEY[] = "defaults_mode";
4447
static const char EQ = '=';
4548
static const char LEFT_BRACKET = '[';
@@ -74,7 +77,8 @@ namespace Aws
7477
{EXTERNAL_ID_KEY, &Profile::SetExternalId, &Profile::GetExternalId},
7578
{CREDENTIAL_PROCESS_COMMAND, &Profile::SetCredentialProcess, &Profile::GetCredentialProcess},
7679
{SOURCE_PROFILE_KEY, &Profile::SetSourceProfile, &Profile::GetSourceProfile},
77-
{DEFAULTS_MODE_KEY, &Profile::SetDefaultsMode, &Profile::GetDefaultsMode}};
80+
{DEFAULTS_MODE_KEY, &Profile::SetDefaultsMode, &Profile::GetDefaultsMode},
81+
{ENDPOINT_URL_KEY, &Profile::SetGlobalEndpointUrl, &Profile::GetGlobalEndpointUrl}};
7882

7983
template<typename EntryT, size_t N>
8084
const EntryT* FindInStaticArray(const EntryT (&array)[N], const Aws::String& searchKey)
@@ -119,6 +123,7 @@ namespace Aws
119123
static const size_t ASSUME_EMPTY_LEN = 3;
120124
State currentState = START;
121125
Aws::String currentSectionName;
126+
Aws::String activeServiceId;
122127
Aws::Map<Aws::String, Aws::String> currentKeyValues;
123128

124129
Aws::String rawLine;
@@ -142,6 +147,7 @@ namespace Aws
142147
{
143148
FlushSection(currentState, currentSectionName, currentKeyValues);
144149
currentKeyValues.clear();
150+
activeServiceId.clear();
145151
ParseSectionDeclaration(line, currentSectionName, currentState);
146152
continue;
147153
}
@@ -158,6 +164,36 @@ namespace Aws
158164
}
159165
}
160166

167+
if(SERVICES_FOUND == currentState)
168+
{
169+
auto equalsPos = line.find(EQ);
170+
if (equalsPos == std::string::npos) {
171+
continue; // ignore garbage/blank in services section
172+
}
173+
174+
auto left = StringUtils::Trim(line.substr(0, equalsPos).c_str());
175+
auto right = StringUtils::Trim(line.substr(equalsPos + 1).c_str());
176+
177+
// New service block: "s3 =" (right hand side empty)
178+
if (!left.empty() && right.empty()) {
179+
activeServiceId = StringUtils::ToUpper(left.c_str());
180+
StringUtils::Replace(activeServiceId, " ", "_");
181+
continue;
182+
}
183+
184+
// Ignore global endpoint_url in [services name] section
185+
if (activeServiceId.empty() && StringUtils::CaselessCompare(left.c_str(), ENDPOINT_URL_KEY) == 0) {
186+
AWS_LOGSTREAM_DEBUG(PARSER_TAG, "Ignoring global endpoint_url in [services " << currentSectionName << "]");
187+
continue;
188+
}
189+
190+
// Property inside an active block: "endpoint_url = http://..."
191+
if (!activeServiceId.empty() && left == ENDPOINT_URL_KEY) {
192+
m_services[currentSectionName][activeServiceId] = right;
193+
continue;
194+
}
195+
}
196+
161197
if(UNKNOWN_SECTION_FOUND == currentState)
162198
{
163199
// skip any unknown sections
@@ -171,6 +207,22 @@ namespace Aws
171207

172208
FlushSection(currentState, currentSectionName, currentKeyValues);
173209

210+
// Resolve service endpoints
211+
for (auto& profilePair : m_foundProfiles)
212+
{
213+
Profile& profile = profilePair.second;
214+
const Aws::String& servicesRef = profile.GetValue("services");
215+
if (!servicesRef.empty())
216+
{
217+
auto servicesBlk = m_services.find(servicesRef);
218+
Aws::Map<Aws::String, Aws::String> endpoints;
219+
if (servicesBlk != m_services.end()) {
220+
endpoints = servicesBlk->second;
221+
}
222+
profile.SetServices(Profile::Services(std::move(endpoints), servicesRef));
223+
}
224+
}
225+
174226
// Put sso-sessions into profiles
175227
for(auto& profile : m_foundProfiles)
176228
{
@@ -222,6 +274,7 @@ namespace Aws
222274
START = 0,
223275
PROFILE_FOUND,
224276
SSO_SESSION_FOUND,
277+
SERVICES_FOUND,
225278
UNKNOWN_SECTION_FOUND,
226279
FAILURE
227280
};
@@ -271,8 +324,9 @@ namespace Aws
271324

272325
/**
273326
* A helper function to parse config section declaration line
274-
* @param line, an input line, e.g. "[profile default]"
327+
* @param line, an input line, e.g. "[profile default]" or "[services s3]"
275328
* @param ioSectionName, a return argument representing parsed section Identifier, e.g. "default"
329+
* @param ioServiceId, a return argument representing parsed service ID for services sections
276330
* @param ioState, a return argument representing parser state, e.g. PROFILE_FOUND
277331
*/
278332
void ParseSectionDeclaration(const Aws::String& line,
@@ -331,21 +385,21 @@ namespace Aws
331385

332386
if(defaultProfileOrSsoSectionRequired)
333387
{
334-
if (sectionIdentifier != DEFAULT && sectionIdentifier != SSO_SESSION_SECTION)
388+
if (sectionIdentifier != DEFAULT && sectionIdentifier != SSO_SESSION_SECTION && sectionIdentifier != SERVICES_SECTION)
335389
{
336390
AWS_LOGSTREAM_ERROR(PARSER_TAG, "In configuration files, the profile name must start with "
337391
"profile keyword (except default profile): " << line);
338392
break;
339393
}
340-
if (sectionIdentifier != SSO_SESSION_SECTION)
394+
if (sectionIdentifier != SSO_SESSION_SECTION && sectionIdentifier != SERVICES_SECTION)
341395
{
342396
// profile found, still pending check for closing bracket
343397
ioState = PROFILE_FOUND;
344398
ioSectionName = sectionIdentifier;
345399
}
346400
}
347401

348-
if(!m_useProfilePrefix || sectionIdentifier != SSO_SESSION_SECTION)
402+
if(!m_useProfilePrefix || (sectionIdentifier != SSO_SESSION_SECTION && sectionIdentifier != SERVICES_SECTION))
349403
{
350404
// profile found, still pending check for closing bracket
351405
ioState = PROFILE_FOUND;
@@ -374,6 +428,32 @@ namespace Aws
374428
ioSectionName = sectionIdentifier;
375429
}
376430

431+
if(sectionIdentifier == SERVICES_SECTION)
432+
{
433+
// Check if this is [services] or [services name]
434+
pos = line.find_first_not_of(WHITESPACE_CHARACTERS, pos);
435+
if(pos == Aws::String::npos || line[pos] == RIGHT_BRACKET)
436+
{
437+
// This is just [services] section
438+
AWS_LOGSTREAM_ERROR(PARSER_TAG, "[services] section without name is not supported: " << line);
439+
break;
440+
}
441+
else
442+
{
443+
// This is [services name] section
444+
sectionIdentifier = ParseIdentifier(line, pos, errorMsg);
445+
if (!errorMsg.empty())
446+
{
447+
AWS_LOGSTREAM_ERROR(PARSER_TAG, "Failed to parse services definition name: " << errorMsg << " " << line);
448+
break;
449+
}
450+
pos += sectionIdentifier.length();
451+
// services definition found, still pending check for closing bracket
452+
ioState = SERVICES_FOUND;
453+
ioSectionName = sectionIdentifier;
454+
}
455+
}
456+
377457
pos = line.find_first_not_of(WHITESPACE_CHARACTERS, pos);
378458
if(pos == Aws::String::npos)
379459
{
@@ -394,7 +474,7 @@ namespace Aws
394474
break;
395475
}
396476
// the rest is a comment, and we don't care about it.
397-
if ((ioState != SSO_SESSION_FOUND && ioState != PROFILE_FOUND) || ioSectionName.empty())
477+
if ((ioState != SSO_SESSION_FOUND && ioState != PROFILE_FOUND && ioState != SERVICES_FOUND) || ioSectionName.empty())
398478
{
399479
AWS_LOGSTREAM_FATAL(PARSER_TAG, "Unexpected parser state after attempting to parse section " << line);
400480
break;
@@ -412,6 +492,7 @@ namespace Aws
412492
* (i.e. [profile default] and its key1=val1 under).
413493
* @param currentState, a current parser State, e.g. PROFILE_FOUND
414494
* @param currentSectionName, a current section identifier, e.g. "default"
495+
* @param currentServiceId, a current service identifier for services sections
415496
* @param currentKeyValues, a map of parsed key-value properties of a section definition being recorded
416497
*/
417498
void FlushSection(const State currentState, const Aws::String& currentSectionName, Aws::Map<Aws::String, Aws::String>& currentKeyValues)
@@ -529,6 +610,10 @@ namespace Aws
529610
ssoSession.SetName(currentSectionName);
530611
ssoSession.SetAllKeyValPairs(std::move(currentKeyValues));
531612
}
613+
else if (SERVICES_FOUND == currentState) {
614+
// Handle [services name] section - service endpoints are parsed inline during stream processing
615+
AWS_LOGSTREAM_DEBUG(PARSER_TAG, "Processed [services " << currentSectionName << "] section");
616+
}
532617
else
533618
{
534619
AWS_LOGSTREAM_FATAL(PARSER_TAG, "Unknown parser error: unexpected state " << currentState);
@@ -557,6 +642,7 @@ namespace Aws
557642

558643
Aws::Map<String, Profile> m_foundProfiles;
559644
Aws::Map<String, Profile::SsoSession> m_foundSsoSessions;
645+
Aws::Map<String, Aws::Map<String, String>> m_services;
560646
};
561647

562648
static const char* const CONFIG_FILE_LOADER = "Aws::Config::AWSConfigFileProfileConfigLoader";

0 commit comments

Comments
 (0)