Skip to content

Commit 5f61ca1

Browse files
committed
updated implementation to correctly use specification
1 parent 0105668 commit 5f61ca1

File tree

4 files changed

+186
-49
lines changed

4 files changed

+186
-49
lines changed

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
#include <aws/core/config/AWSProfileConfigLoaderBase.h>
99

1010
#include <aws/core/utils/memory/stl/AWSString.h>
11+
#include <aws/core/utils/memory/stl/AWSMap.h>
1112

1213
namespace Aws
1314
{
@@ -56,6 +57,7 @@ namespace Aws
5657
private:
5758
Aws::String m_fileName;
5859
bool m_useProfilePrefix;
60+
Aws::Map<Aws::String, Aws::Map<Aws::String, Aws::String>> m_servicesDefinitions;
5961
};
6062
}
6163
}

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

Lines changed: 4 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -94,20 +94,11 @@ namespace Aws
9494
return iter->second;
9595
}
9696

97+
inline const Aws::String& GetServicesDefinitionName() const { return m_servicesDefinitionName; }
98+
inline void SetServicesDefinitionName(const Aws::String& value) { m_servicesDefinitionName = value; }
99+
97100
inline const Aws::String& GetEndpointUrl() const { return m_endpointUrl; }
98101
inline void SetEndpointUrl(const Aws::String& value) { m_endpointUrl = value; }
99-
100-
inline const Aws::UnorderedMap<Aws::String, Aws::String>& GetServicesEndpointUrl() const { return m_servicesEndpointUrl; }
101-
inline void SetServiceEndpointUrl(const Aws::String& serviceId, const Aws::String& endpointUrl)
102-
{
103-
m_servicesEndpointUrl[Aws::Utils::StringUtils::ToLower(serviceId.c_str())] = endpointUrl;
104-
}
105-
106-
const Aws::String* GetServiceEndpointUrl(const Aws::String& serviceId) const
107-
{
108-
auto it = m_servicesEndpointUrl.find(Aws::Utils::StringUtils::ToLower(serviceId.c_str()));
109-
return (it == m_servicesEndpointUrl.end()) ? nullptr : &it->second;
110-
}
111102

112103
inline bool IsSsoSessionSet() const { return m_ssoSessionSet; }
113104
inline const SsoSession& GetSsoSession() const { return m_ssoSession; }
@@ -127,8 +118,8 @@ namespace Aws
127118
Aws::String m_ssoAccountId;
128119
Aws::String m_ssoRoleName;
129120
Aws::String m_defaultsMode;
121+
Aws::String m_servicesDefinitionName;
130122
Aws::String m_endpointUrl;
131-
Aws::UnorderedMap<Aws::String, Aws::String> m_servicesEndpointUrl;
132123
Aws::Map<Aws::String, Aws::String> m_allKeyValPairs;
133124

134125
bool m_ssoSessionSet = false;

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

Lines changed: 61 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ namespace Aws
7676
{EXTERNAL_ID_KEY, &Profile::SetExternalId, &Profile::GetExternalId},
7777
{CREDENTIAL_PROCESS_COMMAND, &Profile::SetCredentialProcess, &Profile::GetCredentialProcess},
7878
{SOURCE_PROFILE_KEY, &Profile::SetSourceProfile, &Profile::GetSourceProfile},
79+
{SERVICES_SECTION, &Profile::SetServicesDefinitionName, &Profile::GetServicesDefinitionName},
7980
{ENDPOINT_URL_KEY, &Profile::SetEndpointUrl, &Profile::GetEndpointUrl},
8081
{DEFAULTS_MODE_KEY, &Profile::SetDefaultsMode, &Profile::GetDefaultsMode}};
8182

@@ -116,13 +117,14 @@ namespace Aws
116117
{}
117118

118119
const Aws::Map<String, Profile>& GetProfiles() const { return m_foundProfiles; }
120+
const Aws::Map<String, Aws::Map<String, String>>& GetServicesDefinitions() const { return m_servicesDefinitions; }
119121

120122
void ParseStream(Aws::IStream& stream)
121123
{
122124
static const size_t ASSUME_EMPTY_LEN = 3;
123125
State currentState = START;
124126
Aws::String currentSectionName;
125-
Aws::String currentServiceId;
127+
Aws::String activeServiceId;
126128
Aws::Map<Aws::String, Aws::String> currentKeyValues;
127129

128130
Aws::String rawLine;
@@ -144,14 +146,14 @@ namespace Aws
144146

145147
if(openPos != std::string::npos && closePos != std::string::npos)
146148
{
147-
FlushSection(currentState, currentSectionName, currentServiceId, currentKeyValues);
149+
FlushSection(currentState, currentSectionName, currentKeyValues);
148150
currentKeyValues.clear();
149-
currentServiceId.clear();
150-
ParseSectionDeclaration(line, currentSectionName, currentServiceId, currentState);
151+
activeServiceId.clear();
152+
ParseSectionDeclaration(line, currentSectionName, currentState);
151153
continue;
152154
}
153155

154-
if(PROFILE_FOUND == currentState || SSO_SESSION_FOUND == currentState || SERVICES_SERVICE_FOUND == currentState)
156+
if(PROFILE_FOUND == currentState || SSO_SESSION_FOUND == currentState)
155157
{
156158
auto equalsPos = line.find(EQ);
157159
if (equalsPos != std::string::npos)
@@ -165,10 +167,30 @@ namespace Aws
165167

166168
if(SERVICES_FOUND == currentState)
167169
{
168-
// Skip key-value pairs in [services] section without service ID
169170
auto equalsPos = line.find(EQ);
170-
if (equalsPos != std::string::npos)
171-
{
171+
if (equalsPos == std::string::npos) {
172+
continue; // ignore garbage/blank in services section
173+
}
174+
175+
auto left = StringUtils::Trim(line.substr(0, equalsPos).c_str());
176+
auto right = StringUtils::Trim(line.substr(equalsPos + 1).c_str());
177+
178+
// New service block: "s3 =" (right hand side empty)
179+
if (!left.empty() && right.empty()) {
180+
activeServiceId = StringUtils::ToLower(left.c_str());
181+
StringUtils::Replace(activeServiceId, " ", "_");
182+
continue;
183+
}
184+
185+
// Ignore top-level endpoint_url in [services name] section
186+
if (activeServiceId.empty() && StringUtils::CaselessCompare(left.c_str(), ENDPOINT_URL_KEY) == 0) {
187+
AWS_LOGSTREAM_DEBUG(PARSER_TAG, "Ignoring global endpoint_url in [services " << currentSectionName << "]");
188+
continue;
189+
}
190+
191+
// Property inside an active block: "endpoint_url = http://..."
192+
if (!activeServiceId.empty() && left == ENDPOINT_URL_KEY) {
193+
m_servicesDefinitions[currentSectionName][activeServiceId] = right;
172194
continue;
173195
}
174196
}
@@ -184,7 +206,7 @@ namespace Aws
184206
break;
185207
}
186208

187-
FlushSection(currentState, currentSectionName, currentServiceId, currentKeyValues);
209+
FlushSection(currentState, currentSectionName, currentKeyValues);
188210

189211
// Put sso-sessions into profiles
190212
for(auto& profile : m_foundProfiles)
@@ -238,7 +260,6 @@ namespace Aws
238260
PROFILE_FOUND,
239261
SSO_SESSION_FOUND,
240262
SERVICES_FOUND,
241-
SERVICES_SERVICE_FOUND,
242263
UNKNOWN_SECTION_FOUND,
243264
FAILURE
244265
};
@@ -295,7 +316,6 @@ namespace Aws
295316
*/
296317
void ParseSectionDeclaration(const Aws::String& line,
297318
Aws::String& ioSectionName,
298-
Aws::String& ioServiceId,
299319
State& ioState)
300320
{
301321
do { // goto in a form of "do { break; } while(0);"
@@ -395,28 +415,27 @@ namespace Aws
395415

396416
if(sectionIdentifier == SERVICES_SECTION)
397417
{
398-
// Check if this is [services] or [services serviceId]
418+
// Check if this is [services] or [services name]
399419
pos = line.find_first_not_of(WHITESPACE_CHARACTERS, pos);
400420
if(pos == Aws::String::npos || line[pos] == RIGHT_BRACKET)
401421
{
402422
// This is just [services] section
403-
ioState = SERVICES_FOUND;
404-
ioSectionName = sectionIdentifier;
423+
AWS_LOGSTREAM_ERROR(PARSER_TAG, "[services] section without name is not supported: " << line);
424+
break;
405425
}
406426
else
407427
{
408-
// This is [services serviceId] section
428+
// This is [services name] section
409429
sectionIdentifier = ParseIdentifier(line, pos, errorMsg);
410430
if (!errorMsg.empty())
411431
{
412-
AWS_LOGSTREAM_ERROR(PARSER_TAG, "Failed to parse service identifier: " << errorMsg << " " << line);
432+
AWS_LOGSTREAM_ERROR(PARSER_TAG, "Failed to parse services definition name: " << errorMsg << " " << line);
413433
break;
414434
}
415435
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());
436+
// services definition found, still pending check for closing bracket
437+
ioState = SERVICES_FOUND;
438+
ioSectionName = sectionIdentifier;
420439
}
421440
}
422441

@@ -440,7 +459,7 @@ namespace Aws
440459
break;
441460
}
442461
// the rest is a comment, and we don't care about it.
443-
if ((ioState != SSO_SESSION_FOUND && ioState != PROFILE_FOUND && ioState != SERVICES_FOUND && ioState != SERVICES_SERVICE_FOUND) || ioSectionName.empty())
462+
if ((ioState != SSO_SESSION_FOUND && ioState != PROFILE_FOUND && ioState != SERVICES_FOUND) || ioSectionName.empty())
444463
{
445464
AWS_LOGSTREAM_FATAL(PARSER_TAG, "Unexpected parser state after attempting to parse section " << line);
446465
break;
@@ -461,7 +480,7 @@ namespace Aws
461480
* @param currentServiceId, a current service identifier for services sections
462481
* @param currentKeyValues, a map of parsed key-value properties of a section definition being recorded
463482
*/
464-
void FlushSection(const State currentState, const Aws::String& currentSectionName, const Aws::String& currentServiceId, Aws::Map<Aws::String, Aws::String>& currentKeyValues)
483+
void FlushSection(const State currentState, const Aws::String& currentSectionName, Aws::Map<Aws::String, Aws::String>& currentKeyValues)
465484
{
466485
if(START == currentState || currentSectionName.empty())
467486
{
@@ -576,20 +595,9 @@ namespace Aws
576595
ssoSession.SetName(currentSectionName);
577596
ssoSession.SetAllKeyValPairs(std::move(currentKeyValues));
578597
}
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-
}
590598
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");
599+
// Handle [services name] section - service endpoints are parsed inline during stream processing
600+
AWS_LOGSTREAM_DEBUG(PARSER_TAG, "Processed [services " << currentSectionName << "] section");
593601
}
594602
else
595603
{
@@ -619,6 +627,7 @@ namespace Aws
619627

620628
Aws::Map<String, Profile> m_foundProfiles;
621629
Aws::Map<String, Profile::SsoSession> m_foundSsoSessions;
630+
Aws::Map<String, Aws::Map<String, String>> m_servicesDefinitions;
622631
};
623632

624633
static const char* const CONFIG_FILE_LOADER = "Aws::Config::AWSConfigFileProfileConfigLoader";
@@ -633,13 +642,15 @@ namespace Aws
633642
bool AWSConfigFileProfileConfigLoader::LoadInternal()
634643
{
635644
m_profiles.clear();
645+
m_servicesDefinitions.clear();
636646

637647
Aws::IFStream inputFile(m_fileName.c_str());
638648
if(inputFile)
639649
{
640650
ConfigFileProfileFSM parser(m_useProfilePrefix);
641651
parser.ParseStream(inputFile);
642652
m_profiles = parser.GetProfiles();
653+
m_servicesDefinitions = parser.GetServicesDefinitions();
643654
return m_profiles.size() > 0;
644655
}
645656

@@ -735,7 +746,21 @@ namespace Aws
735746
{
736747
return nullptr;
737748
}
738-
return profileIter->second.GetServiceEndpointUrl(serviceId);
749+
750+
const auto& servicesDefName = profileIter->second.GetServicesDefinitionName();
751+
if (servicesDefName.empty()) {
752+
return nullptr;
753+
}
754+
755+
auto servicesDefIter = m_servicesDefinitions.find(servicesDefName);
756+
if (servicesDefIter == m_servicesDefinitions.end()) {
757+
return nullptr;
758+
}
759+
760+
Aws::String key = StringUtils::ToLower(serviceId.c_str());
761+
StringUtils::Replace(key, " ", "_");
762+
auto serviceIter = servicesDefIter->second.find(key);
763+
return (serviceIter == servicesDefIter->second.end()) ? nullptr : &serviceIter->second;
739764
}
740765

741766
const Aws::String* AWSConfigFileProfileConfigLoader::GetGlobalEndpointUrl(const Aws::String& profileName) const
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
/**
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
* SPDX-License-Identifier: Apache-2.0.
4+
*/
5+
#include <aws/testing/AwsCppSdkGTestSuite.h>
6+
#include <aws/core/config/AWSProfileConfigLoader.h>
7+
#include <aws/core/utils/FileSystemUtils.h>
8+
#include <aws/core/utils/memory/stl/AWSStreamFwd.h>
9+
#include <fstream>
10+
11+
using namespace Aws::Utils;
12+
using namespace Aws::Config;
13+
14+
class ServiceEndpointsConfigFileLoaderTest : public Aws::Testing::AwsCppSdkGTestSuite
15+
{
16+
};
17+
18+
TEST_F(ServiceEndpointsConfigFileLoaderTest, TestServiceSpecificEndpoints)
19+
{
20+
TempFile configFile(std::ios_base::out | std::ios_base::trunc);
21+
ASSERT_TRUE(configFile.good());
22+
23+
configFile << "[profile default]\n";
24+
configFile << "region = us-west-2\n";
25+
configFile << "endpoint_url = https://global.example.com\n";
26+
configFile << "services = myservices\n";
27+
configFile << "\n[services myservices]\n";
28+
configFile << "s3 =\n";
29+
configFile << " endpoint_url = http://localhost:9000\n";
30+
configFile << "dynamodb =\n";
31+
configFile << " endpoint_url = http://localhost:8000\n";
32+
configFile.flush();
33+
34+
AWSConfigFileProfileConfigLoader loader(configFile.GetFileName(), true);
35+
ASSERT_TRUE(loader.Load());
36+
auto profiles = loader.GetProfiles();
37+
38+
ASSERT_EQ(1u, profiles.size());
39+
ASSERT_NE(profiles.end(), profiles.find("default"));
40+
41+
// Test global endpoint
42+
auto globalEndpoint = loader.GetGlobalEndpointUrl("default");
43+
ASSERT_NE(nullptr, globalEndpoint);
44+
ASSERT_STREQ("https://global.example.com", globalEndpoint->c_str());
45+
46+
// Test service-specific endpoints
47+
auto s3Endpoint = loader.GetServiceEndpointUrl("default", "s3");
48+
ASSERT_NE(nullptr, s3Endpoint);
49+
ASSERT_STREQ("http://localhost:9000", s3Endpoint->c_str());
50+
51+
auto dynamoEndpoint = loader.GetServiceEndpointUrl("default", "dynamodb");
52+
ASSERT_NE(nullptr, dynamoEndpoint);
53+
ASSERT_STREQ("http://localhost:8000", dynamoEndpoint->c_str());
54+
55+
// Test case insensitive service lookup
56+
auto s3EndpointUpper = loader.GetServiceEndpointUrl("default", "S3");
57+
ASSERT_NE(nullptr, s3EndpointUpper);
58+
ASSERT_STREQ("http://localhost:9000", s3EndpointUpper->c_str());
59+
60+
// Test non-existent service
61+
auto nonExistent = loader.GetServiceEndpointUrl("default", "nonexistent");
62+
ASSERT_EQ(nullptr, nonExistent);
63+
64+
// Test non-existent profile
65+
auto nonExistentProfile = loader.GetServiceEndpointUrl("nonexistent", "s3");
66+
ASSERT_EQ(nullptr, nonExistentProfile);
67+
68+
auto nonExistentProfileGlobal = loader.GetGlobalEndpointUrl("nonexistent");
69+
ASSERT_EQ(nullptr, nonExistentProfileGlobal);
70+
}
71+
72+
TEST_F(ServiceEndpointsConfigFileLoaderTest, TestServiceSpecificEndpointsOnly)
73+
{
74+
TempFile configFile(std::ios_base::out | std::ios_base::trunc);
75+
ASSERT_TRUE(configFile.good());
76+
77+
configFile << "[profile test]\n";
78+
configFile << "region = us-east-1\n";
79+
configFile << "services = testservices\n";
80+
configFile << "\n[services testservices]\n";
81+
configFile << "s3 =\n";
82+
configFile << " endpoint_url = http://localhost:9000\n";
83+
configFile.flush();
84+
85+
AWSConfigFileProfileConfigLoader loader(configFile.GetFileName(), true);
86+
ASSERT_TRUE(loader.Load());
87+
88+
// Test that global endpoint is null when not set
89+
auto globalEndpoint = loader.GetGlobalEndpointUrl("test");
90+
ASSERT_EQ(nullptr, globalEndpoint);
91+
92+
// Test service-specific endpoint still works
93+
auto s3Endpoint = loader.GetServiceEndpointUrl("test", "s3");
94+
ASSERT_NE(nullptr, s3Endpoint);
95+
ASSERT_STREQ("http://localhost:9000", s3Endpoint->c_str());
96+
}
97+
98+
TEST_F(ServiceEndpointsConfigFileLoaderTest, TestGlobalEndpointOnly)
99+
{
100+
TempFile configFile(std::ios_base::out | std::ios_base::trunc);
101+
ASSERT_TRUE(configFile.good());
102+
103+
configFile << "[profile test]\n";
104+
configFile << "endpoint_url = https://global.example.com\n";
105+
configFile.flush();
106+
107+
AWSConfigFileProfileConfigLoader loader(configFile.GetFileName(), true);
108+
ASSERT_TRUE(loader.Load());
109+
110+
// Test global endpoint
111+
auto globalEndpoint = loader.GetGlobalEndpointUrl("test");
112+
ASSERT_NE(nullptr, globalEndpoint);
113+
ASSERT_STREQ("https://global.example.com", globalEndpoint->c_str());
114+
115+
// Test that service-specific endpoint is null when not set
116+
auto s3Endpoint = loader.GetServiceEndpointUrl("test", "s3");
117+
ASSERT_EQ(nullptr, s3Endpoint);
118+
}
119+

0 commit comments

Comments
 (0)