Skip to content

Commit cc083f2

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
1 parent e2ffbf9 commit cc083f2

File tree

3 files changed

+122
-10
lines changed

3 files changed

+122
-10
lines changed

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

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,16 @@ namespace Aws
3939
*/
4040
void SetFileName(const Aws::String& fileName) { m_fileName = fileName; }
4141

42+
/**
43+
* Get service-specific endpoint URL for a given profile and service.
44+
*/
45+
const Aws::String* GetServiceEndpointUrl(const Aws::String& profileName, const Aws::String& serviceId) const;
46+
47+
/**
48+
* Get global endpoint URL for a given profile.
49+
*/
50+
const Aws::String* GetGlobalEndpointUrl(const Aws::String& profileName) const;
51+
4252
protected:
4353
virtual bool LoadInternal() override;
4454
virtual bool PersistInternal(const Aws::Map<Aws::String, Aws::Config::Profile>&) override;

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

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77

88
#include <aws/core/utils/memory/stl/AWSString.h>
99
#include <aws/core/utils/memory/stl/AWSMap.h>
10+
#include <aws/core/utils/memory/stl/AWSUnorderedMap.h>
11+
#include <aws/core/utils/StringUtils.h>
1012
#include <aws/core/auth/AWSCredentials.h>
1113

1214
namespace Aws
@@ -93,6 +95,21 @@ namespace Aws
9395
return iter->second;
9496
}
9597

98+
inline const Aws::String& GetEndpointUrl() const { return m_endpointUrl; }
99+
inline void SetEndpointUrl(const Aws::String& value) { m_endpointUrl = value; }
100+
101+
inline const Aws::UnorderedMap<Aws::String, Aws::String>& GetServicesEndpointUrl() const { return m_servicesEndpointUrl; }
102+
inline void SetServiceEndpointUrl(const Aws::String& serviceId, const Aws::String& endpointUrl)
103+
{
104+
m_servicesEndpointUrl[Aws::Utils::StringUtils::ToLower(serviceId.c_str())] = endpointUrl;
105+
}
106+
107+
const Aws::String* GetServiceEndpointUrl(const Aws::String& serviceId) const
108+
{
109+
auto it = m_servicesEndpointUrl.find(Aws::Utils::StringUtils::ToLower(serviceId.c_str()));
110+
return (it == m_servicesEndpointUrl.end()) ? nullptr : &it->second;
111+
}
112+
96113
inline bool IsSsoSessionSet() const { return m_ssoSessionSet; }
97114
inline const SsoSession& GetSsoSession() const { return m_ssoSession; }
98115
inline void SetSsoSession(const SsoSession& value) { m_ssoSessionSet = true; m_ssoSession = value; }
@@ -111,6 +128,8 @@ namespace Aws
111128
Aws::String m_ssoAccountId;
112129
Aws::String m_ssoRoleName;
113130
Aws::String m_defaultsMode;
131+
Aws::String m_endpointUrl;
132+
Aws::UnorderedMap<Aws::String, Aws::String> m_servicesEndpointUrl;
114133
Aws::Map<Aws::String, Aws::String> m_allKeyValPairs;
115134

116135
bool m_ssoSessionSet = false;

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

Lines changed: 93 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ 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";
4345
static const char DEFAULTS_MODE_KEY[] = "defaults_mode";
4446
static const char EQ = '=';
4547
static const char LEFT_BRACKET = '[';
@@ -74,6 +76,7 @@ namespace Aws
7476
{EXTERNAL_ID_KEY, &Profile::SetExternalId, &Profile::GetExternalId},
7577
{CREDENTIAL_PROCESS_COMMAND, &Profile::SetCredentialProcess, &Profile::GetCredentialProcess},
7678
{SOURCE_PROFILE_KEY, &Profile::SetSourceProfile, &Profile::GetSourceProfile},
79+
{ENDPOINT_URL_KEY, &Profile::SetEndpointUrl, &Profile::GetEndpointUrl},
7780
{DEFAULTS_MODE_KEY, &Profile::SetDefaultsMode, &Profile::GetDefaultsMode}};
7881

7982
template<typename EntryT, size_t N>
@@ -119,6 +122,7 @@ namespace Aws
119122
static const size_t ASSUME_EMPTY_LEN = 3;
120123
State currentState = START;
121124
Aws::String currentSectionName;
125+
Aws::String currentServiceId;
122126
Aws::Map<Aws::String, Aws::String> currentKeyValues;
123127

124128
Aws::String rawLine;
@@ -140,13 +144,14 @@ namespace Aws
140144

141145
if(openPos != std::string::npos && closePos != std::string::npos)
142146
{
143-
FlushSection(currentState, currentSectionName, currentKeyValues);
147+
FlushSection(currentState, currentSectionName, currentServiceId, currentKeyValues);
144148
currentKeyValues.clear();
145-
ParseSectionDeclaration(line, currentSectionName, currentState);
149+
currentServiceId.clear();
150+
ParseSectionDeclaration(line, currentSectionName, currentServiceId, currentState);
146151
continue;
147152
}
148153

149-
if(PROFILE_FOUND == currentState || SSO_SESSION_FOUND == currentState)
154+
if(PROFILE_FOUND == currentState || SSO_SESSION_FOUND == currentState || SERVICES_SERVICE_FOUND == currentState)
150155
{
151156
auto equalsPos = line.find(EQ);
152157
if (equalsPos != std::string::npos)
@@ -158,6 +163,16 @@ namespace Aws
158163
}
159164
}
160165

166+
if(SERVICES_FOUND == currentState)
167+
{
168+
// Skip key-value pairs in [services] section without service ID
169+
auto equalsPos = line.find(EQ);
170+
if (equalsPos != std::string::npos)
171+
{
172+
continue;
173+
}
174+
}
175+
161176
if(UNKNOWN_SECTION_FOUND == currentState)
162177
{
163178
// skip any unknown sections
@@ -169,7 +184,7 @@ namespace Aws
169184
break;
170185
}
171186

172-
FlushSection(currentState, currentSectionName, currentKeyValues);
187+
FlushSection(currentState, currentSectionName, currentServiceId, currentKeyValues);
173188

174189
// Put sso-sessions into profiles
175190
for(auto& profile : m_foundProfiles)
@@ -222,6 +237,8 @@ namespace Aws
222237
START = 0,
223238
PROFILE_FOUND,
224239
SSO_SESSION_FOUND,
240+
SERVICES_FOUND,
241+
SERVICES_SERVICE_FOUND,
225242
UNKNOWN_SECTION_FOUND,
226243
FAILURE
227244
};
@@ -271,12 +288,14 @@ namespace Aws
271288

272289
/**
273290
* A helper function to parse config section declaration line
274-
* @param line, an input line, e.g. "[profile default]"
291+
* @param line, an input line, e.g. "[profile default]" or "[services s3]"
275292
* @param ioSectionName, a return argument representing parsed section Identifier, e.g. "default"
293+
* @param ioServiceId, a return argument representing parsed service ID for services sections
276294
* @param ioState, a return argument representing parser state, e.g. PROFILE_FOUND
277295
*/
278296
void ParseSectionDeclaration(const Aws::String& line,
279297
Aws::String& ioSectionName,
298+
Aws::String& ioServiceId,
280299
State& ioState)
281300
{
282301
do { // goto in a form of "do { break; } while(0);"
@@ -331,21 +350,21 @@ namespace Aws
331350

332351
if(defaultProfileOrSsoSectionRequired)
333352
{
334-
if (sectionIdentifier != DEFAULT && sectionIdentifier != SSO_SESSION_SECTION)
353+
if (sectionIdentifier != DEFAULT && sectionIdentifier != SSO_SESSION_SECTION && sectionIdentifier != SERVICES_SECTION)
335354
{
336355
AWS_LOGSTREAM_ERROR(PARSER_TAG, "In configuration files, the profile name must start with "
337356
"profile keyword (except default profile): " << line);
338357
break;
339358
}
340-
if (sectionIdentifier != SSO_SESSION_SECTION)
359+
if (sectionIdentifier != SSO_SESSION_SECTION && sectionIdentifier != SERVICES_SECTION)
341360
{
342361
// profile found, still pending check for closing bracket
343362
ioState = PROFILE_FOUND;
344363
ioSectionName = sectionIdentifier;
345364
}
346365
}
347366

348-
if(!m_useProfilePrefix || sectionIdentifier != SSO_SESSION_SECTION)
367+
if(!m_useProfilePrefix || (sectionIdentifier != SSO_SESSION_SECTION && sectionIdentifier != SERVICES_SECTION))
349368
{
350369
// profile found, still pending check for closing bracket
351370
ioState = PROFILE_FOUND;
@@ -374,6 +393,33 @@ namespace Aws
374393
ioSectionName = sectionIdentifier;
375394
}
376395

396+
if(sectionIdentifier == SERVICES_SECTION)
397+
{
398+
// Check if this is [services] or [services serviceId]
399+
pos = line.find_first_not_of(WHITESPACE_CHARACTERS, pos);
400+
if(pos == Aws::String::npos || line[pos] == RIGHT_BRACKET)
401+
{
402+
// This is just [services] section
403+
ioState = SERVICES_FOUND;
404+
ioSectionName = sectionIdentifier;
405+
}
406+
else
407+
{
408+
// This is [services serviceId] section
409+
sectionIdentifier = ParseIdentifier(line, pos, errorMsg);
410+
if (!errorMsg.empty())
411+
{
412+
AWS_LOGSTREAM_ERROR(PARSER_TAG, "Failed to parse service identifier: " << errorMsg << " " << line);
413+
break;
414+
}
415+
pos += sectionIdentifier.length();
416+
// services serviceId found, still pending check for closing bracket
417+
ioState = SERVICES_SERVICE_FOUND;
418+
ioSectionName = "default"; // Use default profile for services
419+
ioServiceId = StringUtils::ToLower(sectionIdentifier.c_str());
420+
}
421+
}
422+
377423
pos = line.find_first_not_of(WHITESPACE_CHARACTERS, pos);
378424
if(pos == Aws::String::npos)
379425
{
@@ -394,7 +440,7 @@ namespace Aws
394440
break;
395441
}
396442
// the rest is a comment, and we don't care about it.
397-
if ((ioState != SSO_SESSION_FOUND && ioState != PROFILE_FOUND) || ioSectionName.empty())
443+
if ((ioState != SSO_SESSION_FOUND && ioState != PROFILE_FOUND && ioState != SERVICES_FOUND && ioState != SERVICES_SERVICE_FOUND) || ioSectionName.empty())
398444
{
399445
AWS_LOGSTREAM_FATAL(PARSER_TAG, "Unexpected parser state after attempting to parse section " << line);
400446
break;
@@ -412,9 +458,10 @@ namespace Aws
412458
* (i.e. [profile default] and its key1=val1 under).
413459
* @param currentState, a current parser State, e.g. PROFILE_FOUND
414460
* @param currentSectionName, a current section identifier, e.g. "default"
461+
* @param currentServiceId, a current service identifier for services sections
415462
* @param currentKeyValues, a map of parsed key-value properties of a section definition being recorded
416463
*/
417-
void FlushSection(const State currentState, const Aws::String& currentSectionName, Aws::Map<Aws::String, Aws::String>& currentKeyValues)
464+
void FlushSection(const State currentState, const Aws::String& currentSectionName, const Aws::String& currentServiceId, Aws::Map<Aws::String, Aws::String>& currentKeyValues)
418465
{
419466
if(START == currentState || currentSectionName.empty())
420467
{
@@ -529,6 +576,21 @@ namespace Aws
529576
ssoSession.SetName(currentSectionName);
530577
ssoSession.SetAllKeyValPairs(std::move(currentKeyValues));
531578
}
579+
else if (SERVICES_SERVICE_FOUND == currentState) {
580+
// Handle [services serviceId] section
581+
Profile& profile = m_foundProfiles[currentSectionName];
582+
583+
auto endpointUrlIter = currentKeyValues.find(ENDPOINT_URL_KEY);
584+
if (endpointUrlIter != currentKeyValues.end())
585+
{
586+
AWS_LOGSTREAM_DEBUG(PARSER_TAG, "Found service endpoint_url for " << currentServiceId << ": " << endpointUrlIter->second);
587+
profile.SetServiceEndpointUrl(currentServiceId, endpointUrlIter->second);
588+
}
589+
}
590+
else if (SERVICES_FOUND == currentState) {
591+
// Handle [services] section - currently no-op as we ignore key-value pairs here
592+
AWS_LOGSTREAM_DEBUG(PARSER_TAG, "Processed [services] section");
593+
}
532594
else
533595
{
534596
AWS_LOGSTREAM_FATAL(PARSER_TAG, "Unknown parser error: unexpected state " << currentState);
@@ -665,5 +727,26 @@ namespace Aws
665727

666728
return false;
667729
}
730+
731+
const Aws::String* AWSConfigFileProfileConfigLoader::GetServiceEndpointUrl(const Aws::String& profileName, const Aws::String& serviceId) const
732+
{
733+
auto profileIter = m_profiles.find(profileName);
734+
if (profileIter == m_profiles.end())
735+
{
736+
return nullptr;
737+
}
738+
return profileIter->second.GetServiceEndpointUrl(serviceId);
739+
}
740+
741+
const Aws::String* AWSConfigFileProfileConfigLoader::GetGlobalEndpointUrl(const Aws::String& profileName) const
742+
{
743+
auto profileIter = m_profiles.find(profileName);
744+
if (profileIter == m_profiles.end())
745+
{
746+
return nullptr;
747+
}
748+
const Aws::String& endpointUrl = profileIter->second.GetEndpointUrl();
749+
return endpointUrl.empty() ? nullptr : &endpointUrl;
750+
}
668751
} // Config namespace
669752
} // Aws namespace

0 commit comments

Comments
 (0)