{"id":6186,"date":"2025-09-01T00:15:49","date_gmt":"2025-09-01T00:15:49","guid":{"rendered":"https:\/\/techtrendfeed.com\/?p=6186"},"modified":"2025-09-01T00:15:50","modified_gmt":"2025-09-01T00:15:50","slug":"dealing-with-unique-configurations-and-related-templates","status":"publish","type":"post","link":"https:\/\/techtrendfeed.com\/?p=6186","title":{"rendered":"Dealing with Unique Configurations and Related Templates"},"content":{"rendered":"<p> <br \/>\n<\/p>\n<div>\n<h2>Collection Overview<\/h2>\n<p>This text is Half 2.3 of a multi-part sequence: &#8220;Improvement of system configuration administration.&#8221;<\/p>\n<p>The whole sequence:<\/p>\n<ol>\n<li>Introduction<\/li>\n<li>Migration finish evolution\n<ol>\n<li>Working with secrets and techniques, IaC, and deserializing knowledge in Go<\/li>\n<li>Constructing the CLI and API<\/li>\n<li>Dealing with unique configurations and related templates<\/li>\n<\/ol>\n<\/li>\n<li>Efficiency consideration<\/li>\n<li>Abstract and reflections<\/li>\n<\/ol>\n<h2 data-sourcepos=\"1310:1-1310:33\" dir=\"auto\">Unique Hosts Configuration<\/h2>\n<h3 data-sourcepos=\"1311:1-1311:29\" dir=\"auto\">Goal and Necessities<\/h3>\n<p data-sourcepos=\"1312:1-1312:456\" dir=\"auto\">The minimal configuration unit for the brand new SCM is a bunch group that performs the same operate within the infrastructure. In different phrases, we are able to outline this as a task for the hosts. Every function has an setting that describes the host&#8217;s belonging to a devoted set for various functions. For instance, a task known as <strong data-sourcepos=\"1312:314-1312:324\">backend<\/strong> can have three environments: <strong data-sourcepos=\"1312:355-1312:370\">prod, stage,<\/strong> and <strong data-sourcepos=\"1312:376-1312:382\">dev<\/strong> within the venture <strong data-sourcepos=\"1312:399-1312:408\">myproj<\/strong>. Consequently, these names outline the <a rel=\"nofollow\" target=\"_blank\" href=\"http:\/\/Development%20of%20System%20Configuration%20Management%3A%20Introduction\">hostgroup<\/a>:<\/p>\n<ul data-sourcepos=\"1314:1-1317:0\" dir=\"auto\">\n<li data-sourcepos=\"1314:1-1314:20\">myproj-dev-backend<\/li>\n<li data-sourcepos=\"1315:1-1315:22\">myproj-stage-backend<\/li>\n<li data-sourcepos=\"1316:1-1317:0\">myproj-prod-backend<\/li>\n<\/ul>\n<p data-sourcepos=\"1318:1-1318:581\" dir=\"auto\">Every host group consists of a sure variety of hosts, which may generally embrace only one host. Nonetheless, an essential precept that has lengthy been established is that we can not configure objects smaller than a hostgroup utilizing the brand new SCM. This strategy is logical, as host teams are used to allow a number of hosts for horizontal scaling and guarantee excessive availability for every service. Generally, such hosts share the same configuration, and we operated beneath the paradigm that if we would have liked to configure just one host with no hostgroup, we have been doubtless doing one thing fallacious.<\/p>\n<p data-sourcepos=\"1320:1-1320:415\" dir=\"auto\">Nonetheless, we ultimately concluded that there are situations the place extra detailed configurations are mandatory than these relevant to a bunch group. There are additionally many instances the place we have to apply configurations to a number of hosts inside a specific hostgroup. A particular case of this would possibly contain making use of configurations in a canary method to at least one or a couple of hosts to reduce the affect on the remainder of the hostgroup.<\/p>\n<p data-sourcepos=\"1322:1-1322:307\" dir=\"auto\">Within the early phases of growing the brand new SCM, we used a workaround involving stopping the SCM agent or locking updates on some hosts. This allowed us to check deployments on smaller subsets of the host group earlier than making use of the configuration to the complete group. Nonetheless, this strategy has a number of apparent points:<\/p>\n<ul data-sourcepos=\"1323:1-1326:0\" dir=\"auto\">\n<li data-sourcepos=\"1323:1-1323:139\">Testing subsets of servers prevents them from receiving new configuration updates, affecting not simply the half that&#8217;s at the moment being examined.<\/li>\n<li data-sourcepos=\"1324:1-1324:168\">Testing subsets of servers stops checking the verification of the state of the configuration. For example, the SCM agent verifies that the mandatory companies are operating.<\/li>\n<li data-sourcepos=\"1325:1-1326:0\">These handbook operations don&#8217;t enhance general automation.<\/li>\n<\/ul>\n<p data-sourcepos=\"1327:1-1327:107\" dir=\"auto\">In accordance with the above, the next necessities for unique configuration have been established:<\/p>\n<ol data-sourcepos=\"1328:1-1331:0\" dir=\"auto\">\n<li data-sourcepos=\"1328:1-1328:68\">The flexibility to create configurations per host or for a couple of hosts<\/li>\n<li data-sourcepos=\"1329:1-1329:96\">The flexibility to create particular configuration blocks that don&#8217;t intervene with different modules<\/li>\n<li data-sourcepos=\"1330:1-1331:0\">The flexibility to regulate unique configurations remotely to satisfy <a rel=\"nofollow\" target=\"_blank\" href=\"https:\/\/dzone.com\/articles\/what-is-a-cicd-pipeline\">CI\/CD wants<\/a><\/li>\n<\/ol>\n<h3 data-sourcepos=\"1332:1-1332:36\" dir=\"auto\">Doable Methods to Implement This<\/h3>\n<p data-sourcepos=\"1333:1-1333:176\" dir=\"auto\">After we began analyzing potential methods to implement unique configurations, we confronted a key query: the place ought to we retailer the configurations? Primarily, we had two choices.<\/p>\n<p data-sourcepos=\"1335:1-1335:431\" dir=\"auto\">Firstly, it may be saved as a easy file, much like a hostgroup YAML file. This file ought to have a better precedence in comparison with the group configuration. Nonetheless, this strategy shouldn&#8217;t be very helpful as a result of it doesn&#8217;t deal with the third requirement. It can&#8217;t be used from CI with out workarounds. Though we in the end didn&#8217;t select this feature, it grew to become possible to make use of it not directly later, after enabling templating for YAML recordsdata:<\/p>\n<div class=\"codeMirror-wrapper\" contenteditable=\"false\">\n<div contenteditable=\"false\">\n<div class=\"codeMirror-code--wrapper\" data-code=\"{{- if eq .IP &quot;10.10.10.10&quot; }}&#10;configuration for this host only&#10;{{- else }}&#10;configuration for other hosts in hostgroup&#10;{{- end }}\" data-lang=\"text\/x-yaml\">\n<pre><code lang=\"text\/x-yaml\">{{- if eq .IP \"10.10.10.10\" }}\nconfiguration for this host solely\n{{- else }}\nconfiguration for different hosts in hostgroup\n{{- finish }}<\/code><\/pre>\n<\/p><\/div><\/div>\n<\/div>\n<p data-sourcepos=\"1344:1-1344:414\" dir=\"auto\">Secondly, we thought of utilizing an API router for unique configuration management. On this case, we are able to management configurations remotely with correct authorization. For the reason that API makes use of Consul to retailer knowledge, the configuration acquired by the API may be saved in Consul by the SCM, which may then make the most of these keys within the scheduler to construct the entire host configuration. This answer glad all necessities.<\/p>\n<p data-sourcepos=\"1346:1-1346:506\" dir=\"auto\">In follow, implementing this was comparatively simple \u2014 we added this logic on the CLI stage of the deployment device, which processes requests for particular person hosts. The API saves this knowledge in a devoted path: <strong data-sourcepos=\"1346:218-1346:240\">unique\/host\/{ip}<\/strong>. The scheduler then connects to this path when merging configurations, permitting for various configurations for every host. The merged configuration of hosts takes priority, and any beforehand declared configurations may be overwritten by these particular settings.<\/p>\n<p data-sourcepos=\"1348:1-1348:323\" dir=\"auto\">We started utilizing this configuration to implement rollout deployment logic much like that of Kubernetes for non-Docker tasks. The entire logic was built-in on the CLI stage, the place the device acknowledges the variety of hosts in hostgroups, performs deployments throughout the desired variety of hosts, and waits for them to run.<\/p>\n<h3 data-sourcepos=\"1351:1-1351:31\" dir=\"auto\">Implementation within the Code<\/h3>\n<p data-sourcepos=\"1352:1-1352:175\" dir=\"auto\">A route <code data-sourcepos=\"1352:9-1352:26\">api\/v3\/unique<\/code> has been added to the API that gives a CRUD interface. So as to add an unique configuration, ship an HTTP POST request with the next construction:<\/p>\n<div class=\"codeMirror-wrapper\" contenteditable=\"false\">\n<div contenteditable=\"false\">\n<div class=\"codeMirror-code--wrapper\" data-code=\"type ExclusivePostData struct {&#10;    Section string   `json:&quot;section&quot;`&#10;    Hosts   []string `json:&quot;hosts&quot;`&#10;    Ttl     int      `json:&quot;ttl&quot;`&#10;    Data    string   `json:&quot;data&quot;`&#10;    Immediately   bool     `json:&quot;immediately&quot;`&#10;}\" data-lang=\"text\/x-go\">\n<pre><code lang=\"text\/x-go\">kind ExclusivePostData struct {\n    Part string   `json:\"part\"`\n    Hosts   []string `json:\"hosts\"`\n    Ttl     int      `json:\"ttl\"`\n    Knowledge    string   `json:\"knowledge\"`\n    Instantly   bool     `json:\"instantly\"`\n}<\/code><\/pre>\n<\/p><\/div><\/div>\n<\/div>\n<p data-sourcepos=\"1363:1-1368:78\" dir=\"auto\">The place: <code data-sourcepos=\"1364:1-1364:9\">part<\/code> is the context identify for the ensuing JSON despatched to the chosen hosts. <code data-sourcepos=\"1365:1-1365:6\">knowledge<\/code> is the JSON containing the configuration knowledge. <code data-sourcepos=\"1366:1-1366:7\">hosts<\/code> are the chosen hosts. <code data-sourcepos=\"1367:1-1367:5\">ttl<\/code> is the overall time to retailer this key in Consul. <code data-sourcepos=\"1368:1-1368:13\">instantly<\/code> specifies whether or not to deploy instantly utilizing the push strategy.<\/p>\n<p data-sourcepos=\"1370:1-1370:84\" dir=\"auto\">As soon as the API router has acquired all the mandatory knowledge, it saves the important thing in Consul:<\/p>\n<div class=\"codeMirror-wrapper\" contenteditable=\"false\">\n<div contenteditable=\"false\">\n<div class=\"codeMirror-code--wrapper\" data-code=\"consul:~$ consul kv get -recurse exclusive\/10.9.2.116\/&#10;exclusive\/10.9.2.116\/packages:{&quot;iftop&quot;:{}}\" data-lang=\"text\/x-sh\">\n<pre><code lang=\"text\/x-sh\">consul:~$ consul kv get -recurse unique\/10.9.2.116\/\nunique\/10.9.2.116\/packages:{\"iftop\":{}}<\/code><\/pre>\n<\/p><\/div><\/div>\n<\/div>\n<p data-sourcepos=\"1376:1-1376:132\" dir=\"auto\">As talked about earlier, we would have liked to replace the configuration generator. We added one other merge to counterpoint this per-host configuration:<\/p>\n<div class=\"codeMirror-wrapper\" contenteditable=\"false\">\n<div contenteditable=\"false\">\n<div class=\"codeMirror-code--wrapper\" data-code=\"\/\/ merge configuration tree with consul exclusive data&#10;err = mergo.Map(&amp;DefaultConf, ConsulExclusive, mergo.WithOverride, mergo.WithAppendSlice)&#10;if err != nil {&#10;    logger.GenConfLog.Println(&quot;mergo Map failed with DefaultConf and ConsulExclusive&quot;)&#10;    return map[string]interface{}{}, err&#10;}\" data-lang=\"text\/x-go\">\n<pre><code lang=\"text\/x-go\">\/\/ merge configuration tree with consul unique knowledge\nerr = mergo.Map(&amp;DefaultConf, ConsulExclusive, mergo.WithOverride, mergo.WithAppendSlice)\nif err != nil {\n    logger.GenConfLog.Println(\"mergo Map failed with DefaultConf and ConsulExclusive\")\n    return map[string]interface{}{}, err\n}<\/code><\/pre>\n<\/p><\/div><\/div>\n<\/div>\n<p data-sourcepos=\"1386:1-1386:140\" dir=\"auto\">The <code data-sourcepos=\"1386:5-1386:27\">mergo.WithAppendSlice<\/code> flag is especially helpful on this case, because it permits us so as to add info to a slice as a substitute of changing it.<\/p>\n<p data-sourcepos=\"1388:1-1388:101\" dir=\"auto\">Two strategies, GET and DELETE, have been applied for creating and deleting unique configurations.<\/p>\n<h3 data-sourcepos=\"1391:1-1391:32\" dir=\"auto\">Easy methods to Use This in Follow<\/h3>\n<p data-sourcepos=\"1392:1-1392:123\" dir=\"auto\">The <code data-sourcepos=\"1392:5-1392:15\">unique<\/code> command has been added to the CLI device, enabling it to function with the unique API route on the SCM API.<\/p>\n<div class=\"codeMirror-wrapper\" contenteditable=\"false\">\n<div contenteditable=\"false\">\n<div class=\"codeMirror-code--wrapper\" data-code=\"~$ cli exclusive --help&#10;NAME:&#10;   cli exclusive - Get\/push\/delete exclusive configuration for host(s)&#10;&#10;USAGE:&#10;   cli exclusive command [command options] [arguments...]&#10;&#10;COMMANDS:&#10;   push     push exclusive configuration for host(s)&#10;   get      get current exclusive configuration for host(s)&#10;   delete   delete exclusive configuration for host(s)&#10;\u00a0 \u00a0help, h \u00a0Shows a list of commands or help for one command\" data-lang=\"text\/x-nginx-conf\">\n<pre><code lang=\"text\/x-nginx-conf\">~$ cli unique --help\nNAME:\n   cli unique - Get\/push\/delete unique configuration for host(s)\n\nUSAGE:\n   cli unique command [command options] [arguments...]\n\nCOMMANDS:\n   push     push unique configuration for host(s)\n   get      get present unique configuration for host(s)\n   delete   delete unique configuration for host(s)\n\u00a0 \u00a0assist, h \u00a0Reveals a listing of instructions or assist for one command<\/code><\/pre>\n<\/p><\/div><\/div>\n<\/div>\n<p data-sourcepos=\"1408:1-1408:139\" dir=\"auto\">One instance of how unique configurations may be useful is in canary deployments. The next script was applied to be used in CI:<\/p>\n<ol data-sourcepos=\"1410:1-1415:0\" dir=\"auto\">\n<li data-sourcepos=\"1410:1-1410:119\">Sending an unique configuration to the SCM Nginx module to take away one upstream host from the load balancing pool<\/li>\n<li data-sourcepos=\"1411:1-1411:102\">Sending an unique configuration to the SCM Docker module to replace the Docker picture on the host<\/li>\n<li data-sourcepos=\"1412:1-1412:73\">Utilizing numerous checkers to check the appliance for updates and well being<\/li>\n<li data-sourcepos=\"1413:1-1413:103\">Eradicating the unique configuration from the SCM after which enabling site visitors for the up to date backend<\/li>\n<li data-sourcepos=\"1414:1-1415:0\">Repeating this for every host within the subset<\/li>\n<\/ol>\n<h3 data-sourcepos=\"1416:1-1416:52\" dir=\"auto\">Disadvantages of Utilizing Unique Configurations<\/h3>\n<p data-sourcepos=\"1417:1-1417:265\" dir=\"auto\">The primary drawback of unique configurations is their opacity; customers could overlook that they&#8217;ve been created. Whereas there&#8217;s a TTL performance that permits configurations to revert after a specified time, the use case for TTL is primarily appropriate for testing.<\/p>\n<h2 data-sourcepos=\"1421:1-1421:20\" dir=\"auto\">Metrics and Logs<\/h2>\n<p data-sourcepos=\"1422:1-1422:329\" dir=\"auto\">Pull SCM brokers can\u2019t present detailed info when functioning in methods aside from by logs and metrics. We applied completely different loggers at each the agent and API ranges. This enchancment enhanced observability and debugging through the growth of our SCM and stays helpful now that energetic growth is completed.<\/p>\n<p data-sourcepos=\"1424:1-1424:303\" dir=\"auto\">Many loggers write to a number of recordsdata primarily based on their space. Any references to the file module are logged in &#8216;file.log&#8217;, service-related logs go to &#8216;service.log&#8217;, and package-related logs are written to &#8216;package deal.log&#8217;. What in regards to the different modules? Sure, every module has its personal designated log file, similar to:<\/p>\n<ul data-sourcepos=\"1425:1-1429:0\" dir=\"auto\">\n<li data-sourcepos=\"1425:1-1425:15\">aerospike.log<\/li>\n<li data-sourcepos=\"1426:1-1426:11\">nginx.log<\/li>\n<li data-sourcepos=\"1427:1-1427:16\">clickhouse.log<\/li>\n<li data-sourcepos=\"1428:1-1429:0\">and many others.<\/li>\n<\/ul>\n<p data-sourcepos=\"1430:1-1430:197\" dir=\"auto\">On the API stage, configuration mergers write logs to their respective recordsdata. Equally, on the SCM agent stage, parsers additionally log to the related recordsdata. The implementation for that is fairly easy:<\/p>\n<div class=\"codeMirror-wrapper\" contenteditable=\"false\">\n<div contenteditable=\"false\">\n<div class=\"codeMirror-code--wrapper\" data-code=\"func LogInit() {&#10;    PackagesLog = CreateLog(conf.LogPackagesPath, &quot;packages&quot;)&#10;    ServicesLog = CreateLog(conf.LogServicesPath, &quot;services&quot;)&#10;    VaultLog = CreateLog(conf.LogVaultPath, &quot;vault&quot;)&#10;    AerospikeLog = CreateLog(conf.LogAerospiketPath, &quot;aerospike&quot;)&#10;    PKILog = CreateLog(conf.LogPKIPath, &quot;pki&quot;)&#10;    DiskLog = CreateLog(conf.LogDiskPath, &quot;disk&quot;)&#10;    UserLog = CreateLog(conf.LogUserPath, &quot;user&quot;)&#10;}&#10;&#10;func CreateLog(LogPath string, Component string) (*log.Logger){&#10;    file, err := os.OpenFile(Path, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0660)&#10;    if err != nil {&#10;        log.Fatalln(&quot;Failed to open log file&quot;, Path, &quot;:&quot;, err)&#10;    }&#10;    retLogger := log.New(file, &quot;: &quot;, log.Ldate|log.Ltime|log.Lshortfile)&#10;&#10;    SyslogPrefix := os.Getenv(&quot;SYSLOG_PREFIX&quot;)&#10;    SyslogAddr := os.Getenv(&quot;SYSLOG_ENDPOINT&quot;)&#10;    SyslogProto := os.Getenv(&quot;SYSLOG_PROTO&quot;)&#10;    if SyslogPrefix != &quot;&quot; &amp;&amp; Component != &quot;&quot; {&#10;        TagName := SyslogPrefix + &quot;-&quot; + Component&#10;&#10;        if SyslogAddr != &quot;&quot; {&#10;            if SyslogProto == &quot;&quot; {&#10;                SyslogProto = &quot;udp&quot;&#10;            }&#10;&#10;            syslogger, err := syslog.Dial(SyslogProto, SyslogAddr, syslog.LOG_INFO, TagName)&#10;            if err != nil {&#10;                log.Fatalln(err)&#10;            }&#10;&#10;            retLogger.SetOutput(syslogger)&#10;        } else {&#10;            syslogger, err := syslog.New(syslog.LOG_INFO, TagName)&#10;            if err != nil {&#10;                log.Fatalln(err)&#10;            }&#10;&#10;            retLogger.SetOutput(syslogger)&#10;        }&#10;    }&#10;&#10;    return retLogger&#10;}\" data-lang=\"text\/x-go\">\n<pre><code lang=\"text\/x-go\">func LogInit() {\n    PackagesLog = CreateLog(conf.LogPackagesPath, \"packages\")\n    ServicesLog = CreateLog(conf.LogServicesPath, \"companies\")\n    VaultLog = CreateLog(conf.LogVaultPath, \"vault\")\n    AerospikeLog = CreateLog(conf.LogAerospiketPath, \"aerospike\")\n    PKILog = CreateLog(conf.LogPKIPath, \"pki\")\n    DiskLog = CreateLog(conf.LogDiskPath, \"disk\")\n    UserLog = CreateLog(conf.LogUserPath, \"consumer\")\n}\n\nfunc CreateLog(LogPath string, Part string) (*log.Logger){\n    file, err := os.OpenFile(Path, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0660)\n    if err != nil {\n        log.Fatalln(\"Didn't open log file\", Path, \":\", err)\n    }\n    retLogger := log.New(file, \": \", log.Ldate|log.Ltime|log.Lshortfile)\n\n    SyslogPrefix := os.Getenv(\"SYSLOG_PREFIX\")\n    SyslogAddr := os.Getenv(\"SYSLOG_ENDPOINT\")\n    SyslogProto := os.Getenv(\"SYSLOG_PROTO\")\n    if SyslogPrefix != \"\" &amp;&amp; Part != \"\" {\n        TagName := SyslogPrefix + \"-\" + Part\n\n        if SyslogAddr != \"\" {\n            if SyslogProto == \"\" {\n                SyslogProto = \"udp\"\n            }\n\n            syslogger, err := syslog.Dial(SyslogProto, SyslogAddr, syslog.LOG_INFO, TagName)\n            if err != nil {\n                log.Fatalln(err)\n            }\n\n            retLogger.SetOutput(syslogger)\n        } else {\n            syslogger, err := syslog.New(syslog.LOG_INFO, TagName)\n            if err != nil {\n                log.Fatalln(err)\n            }\n\n            retLogger.SetOutput(syslogger)\n        }\n    }\n\n    return retLogger\n}<\/code><\/pre>\n<\/p><\/div><\/div>\n<\/div>\n<p data-sourcepos=\"1479:1-1479:157\" dir=\"auto\">Syslog serves in its place approach to monitor points throughout the infrastructure. Syslog messages are acquired by the native rsyslog and despatched to ElasticSearch.<\/p>\n<p data-sourcepos=\"1481:1-1481:232\" dir=\"auto\">Essentially the most difficult side is logging the variations in recordsdata on account of probably delicate info. These logs are saved solely on the filesystem in &#8216;recordsdata.log&#8217;, with permissions set to 0600, accessible solely to the foundation consumer.<\/p>\n<p data-sourcepos=\"1484:1-1484:133\" dir=\"auto\">With the push scheme, it&#8217;s potential to return HTTP responses indicating the variations to the consumer that initiated the deployment:<\/p>\n<div class=\"codeMirror-wrapper\" contenteditable=\"false\">\n<div contenteditable=\"false\">\n<div class=\"codeMirror-code--wrapper\" data-code=\"func FilesParser(ApiResponse map[string]interface{}, ClientResponse map[string]interface{}) {&#10;    FilesMap, Resp, ActiveParser := ParserGetData(ApiResponse, ClientResponse, &quot;files&quot;)&#10;...&#10;    for FileName, FileOptions := range FilesMap {&#10;...&#10;        GenFile, err := GenTmpFileName(FileName)&#10;        if err != nil {&#10;            logger.FilesLog.Println(&quot;Cannot write temporary file:&quot;, GenFile, &quot;with error&quot;, err)&#10;            Resp[FileName] = map[string]interface{}{&#10;                &quot;state&quot;:            &quot;error&quot;,&#10;                &quot;error&quot;:            err.Error(),&#10;            }&#10;            metrics.FileError.Inc()&#10;            continue&#10;        }&#10;&#10;        err = ioutil.WriteFile(GenFile, data, filemode)&#10;        if err != nil {&#10;            Resp[FileName] = map[string]interface{}{&#10;                &quot;state&quot;:            &quot;error&quot;,&#10;                &quot;error&quot;:            err.Error(),&#10;            }&#10;            logger.FilesLog.Println(&quot;Cannot write to file:&quot;, FileName, &quot;:&quot;, err)&#10;            metrics.FileError.Inc()&#10;            continue&#10;        }&#10;&#10;        err, result = CompareAndCopyFile(FileName, GenFile, filemode, FileUser, FileGroup) {&#10;        if err == nil {&#10;            if result {&#10;                Resp[FileName] = map[string]interface{}{&#10;                    &quot;state&quot;:            &quot;changed&quot;,&#10;                    &quot;diff&quot;:             CalculatedDiff,&#10;                }&#10;                metrics.FileDeployed.Inc()&#10;            }&#10;        } else {&#10;            Resp[FileName] = map[string]interface{}{&#10;                &quot;state&quot;:            &quot;error&quot;,&#10;                &quot;error&quot;:            err.Error(),&#10;            }&#10;            metrics.FileError.Inc()&#10;            continue&#10;        }&#10;    }&#10;...\" data-lang=\"text\/x-go\">\n<pre><code lang=\"text\/x-go\">func FilesParser(ApiResponse map[string]interface{}, ClientResponse map[string]interface{}) {\n    FilesMap, Resp, ActiveParser := ParserGetData(ApiResponse, ClientResponse, \"recordsdata\")\n...\n    for FileName, FileOptions := vary FilesMap {\n...\n        GenFile, err := GenTmpFileName(FileName)\n        if err != nil {\n            logger.FilesLog.Println(\"Can't write non permanent file:\", GenFile, \"with error\", err)\n            Resp[FileName] = map[string]interface{}{\n                \"state\":            \"error\",\n                \"error\":            err.Error(),\n            }\n            metrics.FileError.Inc()\n            proceed\n        }\n\n        err = ioutil.WriteFile(GenFile, knowledge, filemode)\n        if err != nil {\n            Resp[FileName] = map[string]interface{}{\n                \"state\":            \"error\",\n                \"error\":            err.Error(),\n            }\n            logger.FilesLog.Println(\"Can't write to file:\", FileName, \":\", err)\n            metrics.FileError.Inc()\n            proceed\n        }\n\n        err, end result = CompareAndCopyFile(FileName, GenFile, filemode, FileUser, FileGroup) {\n        if err == nil {\n            if end result {\n                Resp[FileName] = map[string]interface{}{\n                    \"state\":            \"modified\",\n                    \"diff\":             CalculatedDiff,\n                }\n                metrics.FileDeployed.Inc()\n            }\n        } else {\n            Resp[FileName] = map[string]interface{}{\n                \"state\":            \"error\",\n                \"error\":            err.Error(),\n            }\n            metrics.FileError.Inc()\n            proceed\n        }\n    }\n...<\/code><\/pre>\n<\/p><\/div><\/div>\n<\/div>\n<p data-sourcepos=\"1533:1-1533:389\" dir=\"auto\">This demonstrates a functioning suggestions system that gives responses to the push brokers. Just like <a rel=\"nofollow\" target=\"_blank\" href=\"https:\/\/dzone.com\/articles\/ansible-security-and-testing-tools-for-automation\">Ansible<\/a> or SaltStack, this method allows the power to watch the outcomes of deployments on hosts and spotlight errors. This instance additionally reveals how the metrics work. We&#8217;ve got quite a lot of metrics; some operate solely on the agent stage, whereas others function on the API\/scheduler stage.<\/p>\n<p data-sourcepos=\"1535:1-1536:101\" dir=\"auto\">Relating to the metrics, we measure the variety of service restarts and reloads, file modifications, tried package deal installations, and extra. Consequently, we are able to monitor infrastructure-level points associated to package deal managers and deployment failures. For instance, the next graph illustrates issues with package deal installations throughout the infrastructure:<\/p>\n<p><img decoding=\"async\" data-new=\"false\" data-mimetype=\"image\/jpeg\" data-creationdateformatted=\"06\/07\/2025 11:28 AM\" data-url=\"https:\/\/dz2cdn1.dzone.com\/storage\/temp\/18457780-1749295721561.jpeg\" data-size=\"346941\" data-id=\"18457780\" class=\"fr-fic fr-dib lazyload\" data-image=\"true\" data-sizeformatted=\"346.9 kB\" data-creationdate=\"1749295723182\" data-type=\"temp\" data-modificationdate=\"null\" data-name=\"1749295721561.jpeg\" src=\"https:\/\/dz2cdn1.dzone.com\/storage\/temp\/18457780-1749295721561.jpeg\" alt=\"Problems with package installations within the infrastructure\"\/><\/p>\n<p data-sourcepos=\"1538:1-1539:105\" dir=\"auto\">One other instance measures the final restarts of companies over the month attributable to our SCM agent:<\/p>\n<p data-sourcepos=\"1538:1-1539:105\" dir=\"auto\"><img decoding=\"async\" data-new=\"false\" data-mimetype=\"image\/jpeg\" data-creationdateformatted=\"06\/07\/2025 05:12 AM\" data-url=\"https:\/\/dz2cdn1.dzone.com\/storage\/temp\/18455627-1749273156739.jpeg\" data-size=\"75807\" data-id=\"18455627\" class=\"fr-fic fr-dib lazyload\" data-image=\"true\" data-sizeformatted=\"75.8 kB\" data-creationdate=\"1749273157721\" data-type=\"temp\" data-modificationdate=\"null\" data-name=\"1749273156739.jpeg\" src=\"https:\/\/dz2cdn1.dzone.com\/storage\/temp\/18455627-1749273156739.jpeg\" alt=\"Last restarts of services over the month\"\/> <a rel=\"nofollow\" target=\"_blank\" href=\"https:\/\/gitlab.com\/iacscm\/doc\/-\/raw\/main\/src\/service-metrics.png?ref_type=heads&amp;inline=false\" rel=\"noopener noreferrer\" target=\"_blank\"\/><\/p>\n<p data-sourcepos=\"1541:1-1541:51\" dir=\"auto\">We even have metrics on the API stage that measure:<\/p>\n<ul data-sourcepos=\"1542:1-1547:0\" dir=\"auto\">\n<li data-sourcepos=\"1542:1-1542:87\">The delta time between the beginning of cache technology for all hosts and its completion<\/li>\n<li data-sourcepos=\"1543:1-1543:67\">Connection timeouts, refusals, and different points with knowledge sources<\/li>\n<li data-sourcepos=\"1544:1-1544:59\">Cache misses when the agent requests recent configurations<\/li>\n<li data-sourcepos=\"1545:1-1545:26\">Replace the speed of the cache<\/li>\n<li data-sourcepos=\"1546:1-1547:0\">Newly found hosts within the infrastructure<\/li>\n<\/ul>\n<h2 data-sourcepos=\"1548:1-1548:24\" dir=\"auto\">Related Templates<\/h2>\n<p data-sourcepos=\"1549:1-1553:148\" dir=\"auto\">In our infrastructure, there should not many causes to make use of the embrace instruction in templates or on the software program stage. For many instances, configurations from templates may be generated robotically, and many people don&#8217;t make modifications to this. A monofile is a more sensible choice on this state of affairs, as managing one file is less complicated than coping with a number of recordsdata in SCM. For instance, if you wish to carry a file to many servers after which resolve to delete it, you would need to create a activity to delete that file and subsequently take away its context. In some instances, folks could overlook to do that and solely delete the file from the SCM repository, leaving it unmanaged by the SCM.<\/p>\n<p data-sourcepos=\"1549:1-1553:148\" dir=\"auto\">With our templating performance, it&#8217;s pointless to create many file consists of, as we&#8217;re assured that our SCM will generate the configuration for any service regardless. Nonetheless, there are some approaches the place this strategy could fail. For instance, there are entities with complicated configurations which can be tough to template. Some software program presents many extra choices than may be outlined by the template, and a few of these choices could also be unstructured. For example, configuring iptables or nftables can have a posh construction.<\/p>\n<p data-sourcepos=\"1549:1-1553:148\" dir=\"auto\">One other space of concern is net servers. We&#8217;ve got discovered that each Nginx and Envoy provide a wealthy set of choices, making template creation for these configurations problematic. Moreover, whereas firewall configurations will not be too giant and will not require many recordsdata, net servers can describe many tasks in a single configuration file. Such configurations can develop to 1 megabyte and even 10MB or extra. Manually finding a selected a part of the configuration inside such a big file is cumbersome, making consists of from the templater important on this case.<\/p>\n<p data-sourcepos=\"1549:1-1553:148\" dir=\"auto\">As talked about earlier, utilizing Nginx-level consists of shouldn&#8217;t be comfy for us as a result of it could result in a lack of management by SCM if a file is deleted. We needed to create templates on the SCM stage and deploy them as a single composed file to the server.<\/p>\n<p data-sourcepos=\"1549:1-1553:148\" dir=\"auto\">To attain this, the Go module <code data-sourcepos=\"1553:32-1553:46\">textual content\/template<\/code> supplies the aptitude to go recordsdata by a New methodology with the template identify and file contents.<\/p>\n<div class=\"codeMirror-wrapper\" contenteditable=\"false\">\n<div contenteditable=\"false\">\n<div class=\"codeMirror-code--wrapper\" data-code=\"func FileTemplateWithIncludes(ApiResopnse map[string]interface{}, templateFileName string, templatesDirs []string) (string, error) {&#10;    templateDirsWithTemplates := make(map[string][]string)&#10;    for _, dir := range templatesDirs {&#10;        dirPath := conf.LConf.FilesDir + &quot;\/data\/&quot; + dir&#10;        templateDirsWithTemplates[dir] = GetFilesFromDirsBySuffix([]string{dirPath}, conf.LConf.TemplateSuffix)&#10;    }&#10;    dataMainFile, err := ioutil.ReadFile(templateFileName)&#10;    if err != nil {&#10;        return string(&quot;&quot;), err&#10;    }&#10;&#10;    tmpl, err := template.New(templateFileName).Funcs(sprig.TxtFuncMap()).Parse(string(dataMainFile))&#10;    if err != nil {&#10;        return string(&quot;&quot;), err&#10;    }&#10;    for relativeTemplateDir, templatesSlice := range templateDirsWithTemplates {&#10;        for _, currtentTemplatePath := range templatesSlice {&#10;            data, err := ioutil.ReadFile(currtentTemplatePath)&#10;            if err != nil {&#10;                continue&#10;            }&#10;            if !strings.HasSuffix(relativeTemplateDir, &quot;\/&quot;) {&#10;                relativeTemplateDir = relativeTemplateDir + &quot;\/&quot;&#10;            }&#10;            templateName := relativeTemplateDir + filepath.Base(currtentTemplatePath)&#10;            _, err = tmpl.New(templateName).Funcs(sprig.TxtFuncMap()).Parse(string(data))&#10;            if err != nil {&#10;                continue&#10;            }&#10;        }&#10;    }&#10;    templateBuffer := new(bytes.Buffer)&#10;    err = tmpl.ExecuteTemplate(templateBuffer, templateFileName, ApiResopnse)&#10;    if err != nil {&#10;        return string(&quot;&quot;), err&#10;    }&#10;    templateBytes, err := ioutil.ReadAll(templateBuffer)&#10;    if err != nil {&#10;        return string(&quot;&quot;), err&#10;    }&#10;    file = string(templateBytes)&#10;    return file, nil&#10;}\" data-lang=\"text\/x-go\">\n<pre><code lang=\"text\/x-go\">func FileTemplateWithIncludes(ApiResopnse map[string]interface{}, templateFileName string, templatesDirs []string) (string, error) {\n    templateDirsWithTemplates := make(map[string][]string)\n    for _, dir := vary templatesDirs {\n        dirPath := conf.LConf.FilesDir + \"\/knowledge\/\" + dir\n        templateDirsWithTemplates[dir] = GetFilesFromDirsBySuffix([]string{dirPath}, conf.LConf.TemplateSuffix)\n    }\n    dataMainFile, err := ioutil.ReadFile(templateFileName)\n    if err != nil {\n        return string(\"\"), err\n    }\n\n    tmpl, err := template.New(templateFileName).Funcs(sprig.TxtFuncMap()).Parse(string(dataMainFile))\n    if err != nil {\n        return string(\"\"), err\n    }\n    for relativeTemplateDir, templatesSlice := vary templateDirsWithTemplates {\n        for _, currtentTemplatePath := vary templatesSlice {\n            knowledge, err := ioutil.ReadFile(currtentTemplatePath)\n            if err != nil {\n                proceed\n            }\n            if !strings.HasSuffix(relativeTemplateDir, \"https:\/\/dzone.com\/\") {\n                relativeTemplateDir = relativeTemplateDir + \"https:\/\/dzone.com\/\"\n            }\n            templateName := relativeTemplateDir + filepath.Base(currtentTemplatePath)\n            _, err = tmpl.New(templateName).Funcs(sprig.TxtFuncMap()).Parse(string(knowledge))\n            if err != nil {\n                proceed\n            }\n        }\n    }\n    templateBuffer := new(bytes.Buffer)\n    err = tmpl.ExecuteTemplate(templateBuffer, templateFileName, ApiResopnse)\n    if err != nil {\n        return string(\"\"), err\n    }\n    templateBytes, err := ioutil.ReadAll(templateBuffer)\n    if err != nil {\n        return string(\"\"), err\n    }\n    file = string(templateBytes)\n    return file, nil\n}<\/code><\/pre>\n<\/p><\/div><\/div>\n<\/div>\n<p data-sourcepos=\"1600:1-1601:137\" dir=\"auto\">Nonetheless, this modifications how we deal with template recordsdata. Beforehand, we templated recordsdata on the agent stage, that means that the templates have been carried to the server as-is, and the file supervisor would template them at that stage. Neither the API nor the scheduler carried out templating at their stage, which distributed the workload throughout the infrastructure. This scheme doesn&#8217;t work for consists of, because it requires all mandatory template recordsdata to be current on the server the place we name the templater. Solely the API has entry to such recordsdata.<\/p>\n<p data-sourcepos=\"1600:1-1601:137\" dir=\"auto\">We now have an implementation wherein the tactic for templating easy templates or templates with consists of is set by a JSON key.<\/p>\n<div class=\"codeMirror-wrapper\" contenteditable=\"false\">\n<div contenteditable=\"false\">\n<div class=\"codeMirror-code--wrapper\" data-code=\"        } else if Fdata.From != &quot;&quot; {&#10;    if Fdata.Template == &quot;goInclude&quot; {&#10;        GoInclude(DefaultConf, FName)&#10;    } else if Fdata.From.(string) != &quot;&quot; {&#10;        filesPath := FilesDir + &quot;\/data\/&quot; + Fdata.From&#10;        Fdata.Datadata, err = ioutil.ReadFile(filesPath)&#10;        if err != nil {&#10;            logger.FilesLog.Println(&quot;file read error&quot;, FileName, err)&#10;        }&#10;    }&#10;}\" data-lang=\"application\/json\">\n<pre><code lang=\"application\/json\">        } else if Fdata.From != \"\" {\n    if Fdata.Template == \"goInclude\" {\n        GoInclude(DefaultConf, FName)\n    } else if Fdata.From.(string) != \"\" {\n        filesPath := FilesDir + \"\/knowledge\/\" + Fdata.From\n        Fdata.Datadata, err = ioutil.ReadFile(filesPath)\n        if err != nil {\n            logger.FilesLog.Println(\"file learn error\", FileName, err)\n        }\n    }\n}<\/code><\/pre>\n<\/p><\/div><\/div>\n<\/div>\n<p data-sourcepos=\"1618:1-1618:304\" dir=\"auto\">Since not many hosts make the most of such giant configurations, this doesn&#8217;t impose a big load on our SCM&#8217;s API. If it does current issues sooner or later, we might implement transferring a specified listing with templates to the agent and reintroduce configuration templating in a distributed method.<\/p>\n<h2 data-sourcepos=\"1621:1-1621:36\" dir=\"auto\">Instance of Making a New Module<\/h2>\n<p data-sourcepos=\"1622:1-1622:193\" dir=\"auto\">To simplify the method of making new modules for brand spanking new group members, we developed a dummy handler that gives a &#8220;spherical cow&#8221; instance of utilization relevant in lots of instances. It seems to be like this:<\/p>\n<div class=\"codeMirror-wrapper newest\" contenteditable=\"false\">\n<div contenteditable=\"false\">\n<div class=\"codeMirror-code--wrapper\" data-code=\"var DummyLog *log.Logger&#10;&#10;func DummyWaitHealthcheck(Dummy DummyType, ApiResponse map[string]interface{}) bool {&#10;    \/\/ check for Dummy service is running&#10;    return true&#10;}&#10;&#10;type DummyType struct {&#10;    State string `json:&quot;state&quot;`&#10;    PackageName string `json:&quot;package_name&quot;`&#10;    ServiceName string `json:&quot;service_name&quot;`&#10;}&#10;&#10;func DummyParser(ApiResponse map[string]interface{}, ClientResponse map[string]interface{}) {&#10;    dummyData, Resp, ActiveParser := common.ParserGetData(ApiResponse, ClientResponse, &quot;dummy&quot;)&#10;    if !ActiveParser {&#10;        return&#10;    }&#10;&#10;    if DummyLog == nil {&#10;        DummyLog = logger.CreateLog(&quot;dummy&quot;)&#10;    }&#10;&#10;    var dummy DummyType&#10;    err := mapstructure.WeakDecode(dummyData, &amp;dummy)&#10;    if err != nil {&#10;        DummyLog.Println(&quot;Err while decode map with mapstructure. Err:&quot;, err)&#10;    }&#10;&#10;&#10;    DummyFlagName := &quot;dummy.service&quot;&#10;&#10;    var Hostgroup string&#10;    if ApiResponse[&quot;Hostgroup&quot;] != nil {&#10;        Hostgroup = ApiResponse[&quot;Hostgroup&quot;].(string)&#10;    }&#10;&#10;    if common.GetFlag(DummyFlagName) {&#10;        LockKey := Hostgroup + &quot;\/&quot; + DummyFlagName&#10;        LockRestartKey := &quot;restart-&quot; + DummyFlagName&#10;&#10;        if common.SharedSelfLock(LockKey, &quot;0&quot;, ApiResponse[&quot;IP&quot;].(string)) {&#10;            DummyLog.Println(&quot;common.SharedSelfLock set ok&quot;)&#10;            if !common.GetFlag(LockRestartKey) {&#10;                DummyLog.Println(&quot;call RollingRestart&quot;)&#10;                common.SetFlag(LockRestartKey)&#10;                common.DaemonReload()&#10;                common.ServiceRestart(DummyFlagName)&#10;            }&#10;        } else {&#10;            DummyLog.Println(&quot;No deploy due to find locks in consul:&quot;, LockKey)&#10;        }&#10;&#10;        DummyLog.Println(&quot;check local flag&quot;, LockRestartKey)&#10;        if common.GetFlag(LockRestartKey) {&#10;            DummyLog.Println(&quot;my flag set&quot;)&#10;            if DummyWaitHealthcheck(dummy, ApiResponse) {&#10;                common.SharedUnlock(LockKey)&#10;                common.DelFlag(LockRestartKey)&#10;                common.DelFlag(DummyFlagName)&#10;            }&#10;        }&#10;        Resp[&quot;status&quot;] = &quot;deploying&quot;&#10;    } else {&#10;        Resp[&quot;status&quot;] = &quot;no changes&quot;&#10;    }&#10;}&#10;&#10;func DummyMerger(ApiResponse map[string]interface{}) {&#10;    dummyData, ActiveParser := common.MergerGetData(ApiResponse, &quot;dummy&quot;)&#10;    if !ActiveParser {&#10;        return&#10;    }&#10;&#10;    if DummyLog == nil {&#10;        DummyLog = logger.CreateLog(&quot;dummy&quot;)&#10;    }&#10;&#10;    var dummy DummyType&#10;    err := mapstructure.Decode(dummyData, &amp;dummy)&#10;    if err != nil {&#10;        DummyLog.Println(&quot;Err while decode map with mapstructure. Err:&quot;, err)&#10;    }&#10;&#10;    DummyService := &quot;dummy.service&quot;&#10;    if dummy.ServiceName != &quot;&quot; {&#10;        DummyService = dummy.ServiceName&#10;    }&#10;    if dummy.State != &quot;&quot; {&#10;        common.APISvcSetState(ApiResponse, DummyService, dummy.State)&#10;    } else {&#10;        common.APISvcSetState(ApiResponse, DummyService, &quot;running&quot;)&#10;    }&#10;&#10;    DummyPackage := &quot;dummy&quot;&#10;    if dummy.PackageName != &quot;&quot; {&#10;        DummyPackage = dummy.PackageName&#10;    }&#10;    common.APIPackagesAdd(ApiResponse, DummyPackage, &quot;&quot;, &quot;&quot;, []string{}, []string{}, []string{})&#10;&#10;    Envs := map[string]interface{}{&#10;        &quot;DUMMY_SERVICE&quot;: &quot;true&quot;,&#10;        &quot;DUMMY_HOST_ID&quot;: &quot;1&quot;,&#10;    }&#10;    common.UsersAdd(ApiResponse, &quot;dummy&quot;, Envs, &quot;&quot;, &quot;&quot;, &quot;&quot;, &quot;&quot;, 0, []string{}, &quot;&quot;, false)&#10;    common.DirectoryAdd(ApiResponse, &quot;\/var\/log\/dummy\/&quot;, &quot;0755&quot;, &quot;dummy&quot;, &quot;nobody&quot;)&#10;&#10;    common.FileAdd(ApiResponse, false, &quot;\/etc\/dummy.conf&quot;, &quot;dummy\/dummy.conf&quot;, &quot;go&quot;, &quot;present&quot;, &quot;root&quot;, &quot;root&quot;, &quot;&quot;, []string{}, []string{}, []string{&quot;dummy.service&quot;}, []string{})&#10;&#10;    Url := &quot;http:\/\/localhost:1112&quot;&#10;&#10;    AlligatorAddAggregate(ApiResponse, &quot;jmx&quot;, Url, []string{})&#10;}\" data-lang=\"text\/x-go\">\n<pre><code lang=\"text\/x-go\">var DummyLog *log.Logger\n\nfunc DummyWaitHealthcheck(Dummy DummyType, ApiResponse map[string]interface{}) bool {\n    \/\/ test for Dummy service is operating\n    return true\n}\n\nkind DummyType struct {\n    State string `json:\"state\"`\n    PackageName string `json:\"package_name\"`\n    ServiceName string `json:\"service_name\"`\n}\n\nfunc DummyParser(ApiResponse map[string]interface{}, ClientResponse map[string]interface{}) {\n    dummyData, Resp, ActiveParser := widespread.ParserGetData(ApiResponse, ClientResponse, \"dummy\")\n    if !ActiveParser {\n        return\n    }\n\n    if DummyLog == nil {\n        DummyLog = logger.CreateLog(\"dummy\")\n    }\n\n    var dummy DummyType\n    err := mapstructure.WeakDecode(dummyData, &amp;dummy)\n    if err != nil {\n        DummyLog.Println(\"Err whereas decode map with mapstructure. Err:\", err)\n    }\n\n\n    DummyFlagName := \"dummy.service\"\n\n    var Hostgroup string\n    if ApiResponse[\"Hostgroup\"] != nil {\n        Hostgroup = ApiResponse[\"Hostgroup\"].(string)\n    }\n\n    if widespread.GetFlag(DummyFlagName) {\n        LockKey := Hostgroup + \"https:\/\/dzone.com\/\" + DummyFlagName\n        LockRestartKey := \"restart-\" + DummyFlagName\n\n        if widespread.SharedSelfLock(LockKey, \"0\", ApiResponse[\"IP\"].(string)) {\n            DummyLog.Println(\"widespread.SharedSelfLock set okay\")\n            if !widespread.GetFlag(LockRestartKey) {\n                DummyLog.Println(\"name RollingRestart\")\n                widespread.SetFlag(LockRestartKey)\n                widespread.DaemonReload()\n                widespread.ServiceRestart(DummyFlagName)\n            }\n        } else {\n            DummyLog.Println(\"No deploy on account of discover locks in consul:\", LockKey)\n        }\n\n        DummyLog.Println(\"test native flag\", LockRestartKey)\n        if widespread.GetFlag(LockRestartKey) {\n            DummyLog.Println(\"my flag set\")\n            if DummyWaitHealthcheck(dummy, ApiResponse) {\n                widespread.SharedUnlock(LockKey)\n                widespread.DelFlag(LockRestartKey)\n                widespread.DelFlag(DummyFlagName)\n            }\n        }\n        Resp[\"status\"] = \"deploying\"\n    } else {\n        Resp[\"status\"] = \"no modifications\"\n    }\n}\n\nfunc DummyMerger(ApiResponse map[string]interface{}) {\n    dummyData, ActiveParser := widespread.MergerGetData(ApiResponse, \"dummy\")\n    if !ActiveParser {\n        return\n    }\n\n    if DummyLog == nil {\n        DummyLog = logger.CreateLog(\"dummy\")\n    }\n\n    var dummy DummyType\n    err := mapstructure.Decode(dummyData, &amp;dummy)\n    if err != nil {\n        DummyLog.Println(\"Err whereas decode map with mapstructure. Err:\", err)\n    }\n\n    DummyService := \"dummy.service\"\n    if dummy.ServiceName != \"\" {\n        DummyService = dummy.ServiceName\n    }\n    if dummy.State != \"\" {\n        widespread.APISvcSetState(ApiResponse, DummyService, dummy.State)\n    } else {\n        widespread.APISvcSetState(ApiResponse, DummyService, \"operating\")\n    }\n\n    DummyPackage := \"dummy\"\n    if dummy.PackageName != \"\" {\n        DummyPackage = dummy.PackageName\n    }\n    widespread.APIPackagesAdd(ApiResponse, DummyPackage, \"\", \"\", []string{}, []string{}, []string{})\n\n    Envs := map[string]interface{}{\n        \"DUMMY_SERVICE\": \"true\",\n        \"DUMMY_HOST_ID\": \"1\",\n    }\n    widespread.UsersAdd(ApiResponse, \"dummy\", Envs, \"\", \"\", \"\", \"\", 0, []string{}, \"\", false)\n    widespread.DirectoryAdd(ApiResponse, \"\/var\/log\/dummy\/\", \"0755\", \"dummy\", \"no person\")\n\n    widespread.FileAdd(ApiResponse, false, \"\/and many others\/dummy.conf\", \"dummy\/dummy.conf\", \"go\", \"current\", \"root\", \"root\", \"\", []string{}, []string{}, []string{\"dummy.service\"}, []string{})\n\n    Url := \"http:\/\/localhost:1112\"\n\n    AlligatorAddAggregate(ApiResponse, \"jmx\", Url, []string{})\n}<\/code><\/pre>\n<\/p><\/div><\/div>\n<\/div>\n<p data-sourcepos=\"1738:1-1738:375\" dir=\"auto\">By merely copying and pasting, discovering and changing, and making a couple of edits, they&#8217;ll create their very own distinctive module to configure another software program utilizing the offered code templates. This strategy is efficient \u2014 many individuals who started growing our SCM began with this expertise. Nonetheless, we nonetheless have too little SCM documentation, however we&#8217;ll work on bettering it.<\/p>\n<\/div>\n\n","protected":false},"excerpt":{"rendered":"<p>Collection Overview This text is Half 2.3 of a multi-part sequence: &#8220;Improvement of system configuration administration.&#8221; The whole sequence: Introduction Migration finish evolution Working with secrets and techniques, IaC, and deserializing knowledge in Go Constructing the CLI and API Dealing with unique configurations and related templates Efficiency consideration Abstract and reflections Unique Hosts Configuration Goal [&hellip;]<\/p>\n","protected":false},"author":2,"featured_media":6188,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[56],"tags":[5053,5052,1658,5054],"class_list":["post-6186","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-software","tag-configurations","tag-exclusive","tag-handling","tag-templates"],"_links":{"self":[{"href":"https:\/\/techtrendfeed.com\/index.php?rest_route=\/wp\/v2\/posts\/6186","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/techtrendfeed.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/techtrendfeed.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/techtrendfeed.com\/index.php?rest_route=\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/techtrendfeed.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=6186"}],"version-history":[{"count":1,"href":"https:\/\/techtrendfeed.com\/index.php?rest_route=\/wp\/v2\/posts\/6186\/revisions"}],"predecessor-version":[{"id":6187,"href":"https:\/\/techtrendfeed.com\/index.php?rest_route=\/wp\/v2\/posts\/6186\/revisions\/6187"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/techtrendfeed.com\/index.php?rest_route=\/wp\/v2\/media\/6188"}],"wp:attachment":[{"href":"https:\/\/techtrendfeed.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=6186"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/techtrendfeed.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=6186"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/techtrendfeed.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=6186"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}<!-- This website is optimized by Airlift. Learn more: https://airlift.net. Template:. Learn more: https://airlift.net. Template: 69d9690a190636c2e0989534. Config Timestamp: 2026-04-10 21:18:02 UTC, Cached Timestamp: 2026-06-04 14:57:20 UTC -->