C++ and AWS : Building a DevOps Monitoring and Automation Tool


A Hands-On Guide to System Administration and Infrastructure as Code (IaC).

The following C++ project simulates an Infrastructure as Code (IaC) Monitoring and Automation Tool  which:
  • collects system details
  • store configurations in JSON-like format
  • simulates deploying infrastructure configurations

Output



-------------------------------------------------------------------------------------------------------------------------

Step 1. Set up the project in Visual Studio by creating a new Console App (C++) project.




Step 2: Name the project 'DevOpsTool'.




Step 3: Ensure the IDE is configured to use the Developer PowerShell terminal. This is helpful because the Developer PowerShell is more powerful than a command prompt. 

Source: Visual Studio Developer Command Prompt and Developer PowerShell.




Step 4: Add the required libraries.
    
#include <iostream>
#include <fstream>      // File operations.
#include <string>         // String manipulation.
#include <vector>        // Dynamic arrays.
#include <cstdlib>        // System calls.
#include <windows.h> // Windows API for system details.
#include <sstream>      // String stream for dynamic JSON string creation.




Step 4a: Use using namespace std; to reduce the need for prefixes (std::cout) when using the Standard Library.




Step 5: Define Key Functions.
    The program shall have three primary functions - 
  • Fetch system details: CPU usage, memory, and disk space.
  • Simulate Infrastructure as Code (IaC) in a JSON-like format and simulate resource creation.
  • Logging: Save all actions to a log file.

12 static void fetchSystemDetails() {
13 // Memory status.
14 MEMORYSTATUSEX memoryStatus;
15 memoryStatus.dwLength = sizeof(MEMORYSTATUSEX);
16 if (GlobalMemoryStatusEx(&memoryStatus)) {
17 std:wcout << "" << endl;
18 std::wcout << L"Memory Usage: " << memoryStatus.dwMemoryLoad << L"%" << "\n" << std::endl;
19
20 std::wcout << L"Total Physical Memory: " << memoryStatus.ullTotalPhys / (1024 * 1024) << L" MB" << std::endl;
21 }
22 else {
23 std::wcerr << L"Failed to get memory status." << std::endl;
24 }



Step 6: Build out core functionality.
    1. Fetch system details.

Use the Windows API to collect basic system details.

Line 12: 
12 static void fetchSystemDetails() {

  • The use of the keyword static means that it has internal linkage and is only accessible within this source file. 


Line 14: 
14 MEMORYSTATUSEX memoryStatus;

  • MEMORYSTATUSEX is a Windows API structure which holds system details like total and available memory.
  • memoryStatus is a variable of the type MEMORYSTATUSEX which stores the retrieved memory information.


Line 15
15 memoryStatus dw.Length = sizeof(MEMORYSTATUSEX);

     
  • The keyword sizeof is a compile-time operator which determines the size of a data type or variables in bytes.
  • dw.Length lets Windows know the sizeof the memory information structure is before using it in order to avoid missing important details or crashing.


Line 16: 
16 if(GlobalMemoryStatusEx(&memoryStatus)) {


  • When GlobalMemoryStatusEx (&memoryStatus) is called, Windows does not become confused and correctly writes the memory information inside the form.
  • GlobalMemoryStatusEx(&memoryStatus) is a Windows API function which fills memoryStatus with memory information. &memoryStatus is the pointer which modifies the data directly.
  • The if function is written so that if the function succeeds, then the memory details are printed. 
  • If the if function fails, then an error message is displayed (handled in else).


Line 18: 
18 std::wcout << L"Memory Usage: " << memoryStatus.dwMemoryLoad << L"%" << "\n" << std:: endl;

  • L denotes a wide string literal (wchar_t) so special characters may be stored from different languages. 
  • memoryStatus.dwMemoryLoad represents a percentage of physical memory which is currently in use.


Line 20: 
20 std::wcout << L"Total Physical Memory: " << memoryStatus.ullTotalPhys / (1024 * 1024) << L" MB" << std::endl;

  • L denotes a wide string literal (wchar_t) so special characters may be stored from different languages. 
  • memoryStatus.ullTotalPhys holds the total physical memory in bytes (the total amount of RAM on a local machine).
  • Divide by 1024 twice to convert the memory, since it is in bytes, into megabytes (MB) because 1024 bytes = 1 KB, and 1024 KB = 1 MB.


Lines 22-23: 
22 else {
std::wcerr << L"Failed to get memory status." << std::endl;
}

  • The "else part" checks if "GlobalMemoryStatusEx" was unsuccessful. 
  • std::wcerr is used to print error messages.
  • The "w" means it is for wide characters, and is able to handle things like special characters.



Lines 27 - 29:
26 // Fetch CPU details.
27 SYSTEM_INFO sysInfo;
28 GetSystemInfo(&sysInfo);
29 std::cout << "  Number of Processors: " << sysInfo.dwNumberOfProcessors << "\n";



Line 27:
27 SYSTEM_INFO sysInfo;

  • This is a struct (structure) which holds system information like the number of processors, the type of CPU, and more.
  • sysInfo is the variable where this information shall be stored.

Line 28:
28 GetSystemInfo(&sysInfo);

  • GetSystemInfo is a Windows API function which fills the sysInfo struct with details about the system (for example, how many processors are available).
  • &sysInfo is the address of the sysInfo variable. The address is passed because GetSystemInfo shall fill this box with data. Without the address, GetSystemInfo would not know where to store data.

Line 29:
29 std::cout << " Number of Processors: " << sysInfo.dwNumberOfProcessors << "\n";

  • std::cout is part of the Standard Output Stream (std) which is used to print text to the console.
  • The std:: prefix specifies that cout belongs to the Standard Library (std namespace).
  • <<   is the Insertion Stream Operator. This is used to send output to the std::cout, which prints data to the console.
  • sysInfo is the variable of type SYSTEM_INFO which holds information about the system's hardware. 
  • dwNumberOfProcessors is a member variable of SYSTEM_INFO, which stores the number of logical processors on the system (CPU cores) in the system.
    • For example, if a computer has 4 CPU cores, then this value would be 4.


 Lines 32 - 35:
31  // Disk space.
32 ULARGE_TheINTEGER freeBytesAvailable, totalBytes, totalFreeBytes;
33 if (GetDiskFreeSpaceEx(L"C:\\", &freeBytesAvailable, &totalBytes, &totalFreeBytes)) {
34 std::wcout << L"  Total Disk Space " << totalBytes.QuadPart / (1024 * 1024 * 1024) << " GB\n";
35 std::wcout << L"  Free Disk Space: " << totalFreeBytes.QuadPart / (1024 * 1024 * 1024) << " GB\n";
}


Line 32:
32  ULARGE_INTEGER freeBytesAvailable, totalBytes, totalFreeBytes;

  • Three variables have been declared: freeBytesAvailable, totalBytes, and totalFreeBytes.
  • ULARGE_INTEGER is a Windows-specific data type used to store large unsigned 64-bit integer values.

Line 33:
33 if (GetDiskFreeSpaceEx(L"C:\\", &freeBytesAvailable, &totalBytes, &totalFreeBytes)) {

  • The if part of the if-else statement calls a Window API function to retrieve disk space details for the C:\drive and checks if the function call was successful.
  • The L prefix indicates a wide-character string literal, used for compatibility with wchar_t in Unicode environment. 
  • The &freeBytesAvailable is a pointer to a ULARGE_INTEGER variable which receives the number of free bytes available to the calling process, which is the process which invoked GetDiskFreeSpaceEx and is requesting information about disk space.
  • &totalBytes is the total disk size in bytes.
  • &totalFreeBytes is the total free space on the disk, regardless of user permissions. 


Line 34:
34 std::wcout << L"  Total Disk Space " << totalBytes.QuadPart / (1024 * 1024 * 1024) << " GB\n";

  • std::wcout is used for wide-character output (string literals use "L").
  • "L" Total Disk Space ": L denotes a wide string literal (used for Unicode support).
  • totalBytes.QuadPart : totalBytes is a ULARGE_INTEGER structure.
  • QuadPart is a 64-bit unsigned integer holding the total disk space in bytes.
  • /Dividing by / (1024 * 1024 * 1024) converts bytes into gigabytes (GB). 

Line 35:
35 std::wcout << L"  Free Disk Space: " << totalFreeBytes.QuadPart / (1024 * 1024 * 1024) << " GB\n";

  • std::wcout is used for wide-character output.
  • "L" : denotes a wide string literal (used for Unicode support).
  • totalFreeBytes.QuadPart: totalFreeBytes is a ULARGE_INTEGER structure.
  • .QuadPart is a 64-bit unsigned integer holding the total free disk space in bytes.
  • / Dividing by / (1024 * 1024 * 1024) converts bytes into gigabytes (GB).  

Lines 37 - 38:
36  }
37 else {
38 std::wcerr << L"Failed to get disk space details." << std::endl;
39 }
40  }

  • std::wcerr is used for wide-character error output ( like std::wcout but for errors).
  • Send the message to standard error (stderr) instead of standard output (stdout).
  • "L " ensures Unicode compatibility.
  • The else statement executes and prints "Failed to get disk space details" if the if part of the condition fails to execute.

The void simulateIaC() function covers lines 44 - 73. 
Lines 44 - 49.  

44 void simulateIaC() {
45 std::string resourceName = "web-server";
46 int cpuCores = 2;
47 // In MB.
48 int memorySize = 4096;
49 std::string region = "us-east-1";



Line 44:
44 void simulateIaC() {


  • Declaration of the simulateIaC() function by using void means that the function does not return any value.
  • The () means that the function does not take any parametres. 

Line 45:
45 std::string resourceName = "web-server";

  • std::string is a data type which represents a string.
  • resourceName is the variable name which shall store the string value.
  • = "web-server"; the " = " assigns the value "web-server" to the resourceName variable.
NOTE: "web-server" must be a value declared as a the value "resourceName" and not a variable because variable names may only have letters, numbers, or underscores. Special characters such as a hyphen ( - ) are not allowed.


Line 46:
46 int cpuCores = 2;

  • The variable, "cpuCores", of type int,  is declared as a whole number integer (int) which contains an integer value of 2. 

Line 48:
48 int memorySize = 4096;

  • The variable name, "memorySize", is a declared as a whole number integer (int) which holds the value 4096.
  • The value of 4096 refers to the amount of memory in megabytes (MB) which is being allocated to the web-server to run its processes, applications, and services.

Line 49:
49 std::string region = "us-east-1";

  • std:: is the namespace which is included in the C++ Standard Library and is a way to avoid name conflicts with other libraries or code.
  • string is a type from the C++ Standard Library  which represents a sequence of characters ( "us-east-1").
  • = "us-east-1" is the string value assigned to the variable "region".


Reminder >> The simulateIaC() function covers lines 44 - 73.
Lines 51 - 58: 

51 // Generate JSON configuration.
52 std::stringstream jsonConfig;
53 jsonConfig << "{\n";
54 jsonConfig << "  \"resource\":  \"" << resourceName << "\",\n";
55 jsonConfig << "  \"cpu\": " << cpuCores << ",\n";
56 jsonConfig << "  \"memory\": " << memorySize << ",\n";
57 jsonConfig << "  \"region\": \"" << region << "\"\n";
58 jsonConfig << "}";



Line 52:
52 std::stringstream jsonConfig;

  • std:: is part of the C++ Standard Library and is a namespace which is used to avoid conflicts with other libraries or code.
  • stringstream is part of the #include <sstream> header which allows one to build or read data from a string (text stored in memory).
  • jsonConfig is the stringstream variable and also the object of the stringstream type.  Which means that it is capable of handling and manipulating string data (building strings). Labeled as such because this is to show the values are to be formatted in JSON.
  • The reason it is more effective and efficient to use the <sstream> library is:
    • To avoid having to explicitly convert non-string types or manage temporary strings.
    • Example: 
    • Line 56 (the concatenation of  (string) -> memory + (int)-> memorySize).
    • Improves application performance.
    • Code is clean and human-readable.

Line 53:
53 jsonConfig << "{\n";

  • The new line character "{\n"  helps to ensure that when the JSON is printed, it appears in a properly formatted manner with line breaks.

Lines 54 - 57: 
The explanation for line 54 is the same for lines 55 - 57.

54 jsonConfig << "  \"resource\":  \"" << resourceName << "\",\n";

  • The stringstream object "jsonConfig" adds the key "resource" to the stream and then adds the value (key-value pair used in JSON formatting) "resourceName" which is "web-server".  
  • The output below is what it should look like:


Line 58:
58 jsonConfig << "}";

  • The ending curly brace ( } ) signals the end of the structured JSON string. 
  • The key-value pairs are considered properties of the JSON object; the entire contents, as a whole, contained within the curly braces {}  (the key-value pairs) are considered the JSON object.
  • The ending curly brace ( } ) ensures that the key-value pairs remain properly enclosed and formatted.

Lines 62 - 63:
62 std::cout << "Generated IaC Configuration: \n";
63 std::cout << jsonConfig.str() << "\n";
        


Line 62:


62 std::cout << "Generated IaC Configuration: \n";
  • std:: is the namespace (standard output) which is included in the C++ Standard Library and is a way to avoid name conflicts with other libraries or code.
  • cout is an object of the std::ostream class, which is part of the C++ Standard Library <iostream>.

Line 63:


63    std::cout << jsonConfig.str() << "\n";
  • std:: is the namespace (standard output) which is included in the C++ Standard Library and is a way to avoid name conflicts with other libraries or code.
  • cout is an object of the std::ostream class, which is part of the C++ Standard Library <iostream>.
  • jsonConfig.str() converts the contents of jsonConfig (which was built using std::stringstream) into a string using .str()  " 


 Lines 65-72:
65 // Simulate "applying" the configuration.
66 std::cout << "" << endl;
67 std::cout << "Applying configuration...\n";
68 // Simulate delay.
69 Sleep(2000);
70 std::cout << "" << endl;
71 std::cout << "Infrastructure deployed successfully.\n";
72 std::cout << "" << endl;
}



Line 67:
67 std::cout << "Applying configuration...\n";

  • std:: is the namespace (standard output) which is included in the C++ Standard Library and is a way to avoid name conflicts with other libraries or code.
  • cout is an object of the std::ostream class, which is part of the C++ Standard Library <iostream>.

Line 69:
69 Sleep(2000);
  • sleep(2000);  Pauses the program for 2000 milliseconds, which is 2 seconds.
  • The sleep() command is used to simulate a delay (as if though awaiting for infrastructure deployment). 
  • The sleep() command is a Windows-specific command from the <windows.h> library.

Line 71
71 std::cout << "Infrastructure deployed successfully.\n";

  • std:: is the namespace (standard output) which is included in the C++ Standard Library and is a way to avoid name conflicts with other libraries or code.
  • cout is an object of the std::ostream class, which is part of the C++ Standard <iostream> Library .
  • "\n" returns a newline character, which moves the cursor to the beginning of the next line.

Lines 76 - 83
75   // Log all actions to a file for review.
76   void logAction(const std::string& action) {
77   std::ofstream logFile("DevOpsTool.log", std::ios::app);
78   if (logFile.is_open()) {
79    logFile << action << "\n";
80 logFile.close();
81   }  else {
82 std:cerr << "Error: Unable to open log file.\n";
84       }
85 }

Line 76 
76  void logAction(const std::string& action) {

  • The void function is used when a return value is not expected.
  • 'logAction' is the function name assigned to the void function.
  • The 'const' keyword means the function may not modify 'action'. 
  • std::string& is a data type which represents a string and the '&' eliminates the need to make a copy of the string, making it more efficient.
  •  action means that the function may not modify th e value of action itself inside the logAction() however, the function may still write action into the log file.

Line 77
77   std::ofstream logFile("DevOpsTool.log", std::ios::app);

  • std::ofstream stands for "output file stream" and is a class in the C++ Standard Library used to write to files. ofstream is especially designed for output (writing data to files).
  • std:: refers to the standard namespace and means that features from the Standard Library are being used.
  • logfile is the name of the object of the std::ofstream class. The object is a tool used to interact (write to) the file.
  • "DevOpsTool.log" is the name of the file where actions may be logged.
  • std::ios::app is a namespace that which contains file stream options (input/output modes). 
  • 'app' means append, this is more of an incremental modification (writing to) the file.
  • If one wishes to do a complete write-over, then 'std::ios::trunc' ought to be used.
  • 'trunc' erase's the file's current content and starts fresh each time the program is executed.

Line 78:
78  if (logFile.is_open()) {

  • 'if' is a conditional statement in C++. The 'if' evaluates the given expression (logfile.is_open()) and checks if it is ready to be written to. 
  • logFile is the variable of the ofstream object, which is used for writing files and stands for output file stream.
  • logFile represents the output file stream used for writing to the file.
  • is_open is a member function of the std::ofstream ( output file stream ) class in C++.
  • is_open checks whether a file stream (logFile) has been successfully opened.


Line 79
79  logFile << action << "\n";

  • This line of code writes 'action' to the logFile, and appends a newline ("\n") character at the end, moving to the next line in the file.
  • The " << " stream insertion operator is used to write data to an output stream.


Line 80
80     logFile.close();

  • The logFile.close() command releases the resources allocated by the operating system (file descriptors, memory buffers, etc., ), which prevents memory leaks and ensures the file is not locked. 
  • Closing the file ensures that any remaining data in the buffer is flushed to the file.
  • logFile.close(); is a best practice for properly closing a file.


Line 81
81   }  else {

  • The else part of the conditional if-else statement executes only if 'if (logFile.is_open()) evaluates to false.
  • In other words, if the file fails to open, the else block prints an error message to std::cerr.


Line 82
82      std:cerr << "Error: Unable to open log file.\n";

  • This line prints an error message to standard error output (stdcerr) when the log is not able to be opened. 
  • std::cerr is a special output stream for errors.
  • C++ replaced 'printf' with std::cout and std::cerr in the iostream library.


Lines 87 - 108




87  // Combine everything into the main() function to create a complete code base.
88  int main() {
89 cout << "Welcome to the DevOps Infrastructure Monitoring Tool\n";
90 cout << "=====================================================";
91 cout << "" << endl;
92
93 // Log start of program.
94 logAction("Program started: ");
95
96 // Call the static function and fetch system details.
97 fetchSystemDetails();
98 logAction("System details fetched.");
99
100 // Simulate IaC.
101 simulateIaC();
102 logAction("IaC simulation completed.");
103
104 // End of program.
105 logAction("Program ended.");
106 cout << "----------------------------------------------\n";
107 cout << " **  All actions logged to DevOpTool.log\n";
108
109 return 0;
110 }



















Comments

Popular posts from this blog

C++ script - Script to learn map library | struct | transform a vector of keys

Developer's Arsenal: How HashiCorp unites cloud securite and upholds the AWS Well-Architected Framework when implementing Policy as Code using Sentinel One.

How AWS Protects Your Data Privacy and Security- 2025 January