This is an automated email from the ASF dual-hosted git repository.

neuman pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/trafficcontrol.git


The following commit(s) were added to refs/heads/master by this push:
     new d74de45  Add ORT/atstccfg Deterministic Config Generation (#4557)
d74de45 is described below

commit d74de45534abb15d7c00cbfa49c3a04ea01e2b69
Author: Robert O Butts <rob...@users.noreply.github.com>
AuthorDate: Tue May 26 09:36:58 2020 -0600

    Add ORT/atstccfg Deterministic Config Generation (#4557)
    
    * Make ORT gen deterministic
    
    * Add ORT/atstccfg Line-Comment header
    
    Adds a header to the multipart file, with the line comment syntax of
    the file (if it has one).
    
    This lets ORT safely strip line comments with times for diffing.
    
    * Remove ORT/atstccfg unnecessary io param
    
    Removes TCCfg from GetAllConfigs, making the function Pure, which
    makes it possible/easier to unit test.
    
    * Change ORT/atstccfg magic string to const
    
    * Add ORT/atstccfg unit test for determinism
---
 lib/go-atscfg/astatsdotconfig.go                   |   1 +
 lib/go-atscfg/atscfg.go                            |   9 +-
 lib/go-atscfg/atsdotrules.go                       |   1 +
 lib/go-atscfg/bgfetchdotconfig.go                  |   1 +
 lib/go-atscfg/cachedotconfig.go                    |   8 +-
 lib/go-atscfg/cacheurldotconfig.go                 |   1 +
 lib/go-atscfg/chkconfig.go                         |  16 +
 lib/go-atscfg/dropqstringdotconfig.go              |   1 +
 lib/go-atscfg/facts.go                             |   1 +
 lib/go-atscfg/headerrewritedotconfig.go            |   1 +
 lib/go-atscfg/hostingdotconfig.go                  |  11 +-
 lib/go-atscfg/ipallowdotconfig.go                  |  19 +
 lib/go-atscfg/loggingdotconfig.go                  |   1 +
 lib/go-atscfg/loggingdotyaml.go                    |   1 +
 lib/go-atscfg/logsdotxml.go                        |   6 +
 lib/go-atscfg/packages.go                          |  14 +
 lib/go-atscfg/parentdotconfig.go                   |   6 +-
 lib/go-atscfg/plugindotconfig.go                   |   1 +
 lib/go-atscfg/recordsdotconfig.go                  |   1 +
 lib/go-atscfg/regexremapdotconfig.go               |   1 +
 lib/go-atscfg/regexrevalidatedotconfig.go          |   2 +
 lib/go-atscfg/remapdotconfig.go                    |   1 +
 lib/go-atscfg/servercachedotconfig.go              |  10 +-
 lib/go-atscfg/serverunknown.go                     |  18 +
 lib/go-atscfg/setdscpdotconfig.go                  |   1 +
 lib/go-atscfg/sslmulticertdotconfig.go             |  10 +-
 lib/go-atscfg/storagedotconfig.go                  |   1 +
 lib/go-atscfg/sysctldotconf.go                     |   1 +
 lib/go-atscfg/unknownconfig.go                     |  24 +-
 lib/go-atscfg/urisigningconfig.go                  |   1 +
 lib/go-atscfg/urlsigconfig.go                      |  16 +-
 lib/go-atscfg/volumedotconfig.go                   |   1 +
 traffic_ops/ort/atstccfg/atstccfg.go               |   5 +-
 traffic_ops/ort/atstccfg/cfgfile/all.go            |  13 +-
 .../ort/atstccfg/cfgfile/astatsdotconfig.go        |  13 +-
 traffic_ops/ort/atstccfg/cfgfile/atsdotrules.go    |  13 +-
 .../ort/atstccfg/cfgfile/bgfetchdotconfig.go       |   4 +-
 traffic_ops/ort/atstccfg/cfgfile/cachedotconfig.go |   4 +-
 .../ort/atstccfg/cfgfile/cacheurldotconfig.go      |   6 +-
 traffic_ops/ort/atstccfg/cfgfile/cfgfile.go        |   8 +-
 traffic_ops/ort/atstccfg/cfgfile/cfgfile_test.go   | 469 ++++++++++++++++++++-
 traffic_ops/ort/atstccfg/cfgfile/chkconfig.go      |   4 +-
 .../ort/atstccfg/cfgfile/dropqstringdotconfig.go   |   4 +-
 traffic_ops/ort/atstccfg/cfgfile/facts.go          |   4 +-
 .../ort/atstccfg/cfgfile/headerrewritedotconfig.go |  10 +-
 .../atstccfg/cfgfile/headerrewritemiddotconfig.go  |  10 +-
 .../ort/atstccfg/cfgfile/hostingdotconfig.go       |   6 +-
 .../ort/atstccfg/cfgfile/ipallowdotconfig.go       |   8 +-
 .../ort/atstccfg/cfgfile/loggingdotconfig.go       |   4 +-
 traffic_ops/ort/atstccfg/cfgfile/loggingdotyaml.go |   4 +-
 .../ort/atstccfg/cfgfile/logsxmldotconfig.go       |   4 +-
 traffic_ops/ort/atstccfg/cfgfile/meta.go           |   5 +-
 traffic_ops/ort/atstccfg/cfgfile/packages.go       |   4 +-
 .../ort/atstccfg/cfgfile/parentdotconfig.go        |  36 +-
 .../ort/atstccfg/cfgfile/plugindotconfig.go        |   4 +-
 .../ort/atstccfg/cfgfile/recordsdotconfig.go       |   4 +-
 .../ort/atstccfg/cfgfile/regexremapdotconfig.go    |  10 +-
 .../atstccfg/cfgfile/regexrevalidatedotconfig.go   |   4 +-
 traffic_ops/ort/atstccfg/cfgfile/remapdotconfig.go |  55 +--
 traffic_ops/ort/atstccfg/cfgfile/routing.go        |  63 +--
 .../ort/atstccfg/cfgfile/servercachedotconfig.go   |   6 +-
 .../ort/atstccfg/cfgfile/serverunknownconfig.go    |   5 +-
 .../ort/atstccfg/cfgfile/setdscpdotconfig.go       |   4 +-
 .../ort/atstccfg/cfgfile/sslmulticertdotconfig.go  |   4 +-
 .../ort/atstccfg/cfgfile/storagedotconfig.go       |   4 +-
 traffic_ops/ort/atstccfg/cfgfile/sysctldotconf.go  |  17 +-
 traffic_ops/ort/atstccfg/cfgfile/unknownconfig.go  |   9 +-
 .../ort/atstccfg/cfgfile/urisigningconfig.go       |   8 +-
 traffic_ops/ort/atstccfg/cfgfile/urlsigconfig.go   |  20 +-
 .../ort/atstccfg/cfgfile/volumedotconfig.go        |   4 +-
 traffic_ops/ort/atstccfg/config/config.go          |  13 +
 traffic_ops/ort/atstccfg/plugin/hello_world.go     |   1 +
 traffic_ops/ort/atstccfg/plugin/plugin_test.go     |   1 +
 73 files changed, 851 insertions(+), 210 deletions(-)

diff --git a/lib/go-atscfg/astatsdotconfig.go b/lib/go-atscfg/astatsdotconfig.go
index 0fbc0bf..8ea62d5 100644
--- a/lib/go-atscfg/astatsdotconfig.go
+++ b/lib/go-atscfg/astatsdotconfig.go
@@ -23,6 +23,7 @@ const AstatsSeparator = "="
 const AstatsFileName = "astats.config"
 
 const ContentTypeAstatsDotConfig = ContentTypeTextASCII
+const LineCommentAstatsDotConfig = LineCommentHash
 
 func MakeAStatsDotConfig(
        profileName string,
diff --git a/lib/go-atscfg/atscfg.go b/lib/go-atscfg/atscfg.go
index 2e38e4e..8798ed3 100644
--- a/lib/go-atscfg/atscfg.go
+++ b/lib/go-atscfg/atscfg.go
@@ -21,6 +21,7 @@ package atscfg
 
 import (
        "errors"
+       "sort"
        "strconv"
        "strings"
        "time"
@@ -36,6 +37,8 @@ const HeaderCommentDateFormat = "Mon Jan 2 15:04:05 MST 2006"
 
 const ContentTypeTextASCII = `text/plain; charset=us-ascii`
 
+const LineCommentHash = "#"
+
 type ServerCapability string
 
 type ServerInfo struct {
@@ -103,10 +106,14 @@ func GenericProfileConfig(
        separator string,
 ) string {
        text := ""
+
+       lines := []string{}
        for name, val := range paramData {
                name = trimParamUnderscoreNumSuffix(name)
-               text += name + separator + val + "\n"
+               lines = append(lines, name+separator+val+"\n")
        }
+       sort.Strings(lines)
+       text = strings.Join(lines, "")
        return text
 }
 
diff --git a/lib/go-atscfg/atsdotrules.go b/lib/go-atscfg/atsdotrules.go
index 5ea8f43..286d44e 100644
--- a/lib/go-atscfg/atsdotrules.go
+++ b/lib/go-atscfg/atsdotrules.go
@@ -24,6 +24,7 @@ import (
 )
 
 const ContentTypeATSDotRules = ContentTypeTextASCII
+const LineCommentATSDotRules = LineCommentHash
 
 func MakeATSDotRules(
        profileName string,
diff --git a/lib/go-atscfg/bgfetchdotconfig.go 
b/lib/go-atscfg/bgfetchdotconfig.go
index 3480613..7629e40 100644
--- a/lib/go-atscfg/bgfetchdotconfig.go
+++ b/lib/go-atscfg/bgfetchdotconfig.go
@@ -24,6 +24,7 @@ import (
 )
 
 const ContentTypeBGFetchDotConfig = ContentTypeTextASCII
+const LineCommentBGFetchDotConfig = LineCommentHash
 
 func MakeBGFetchDotConfig(
        cdnName tc.CDNName,
diff --git a/lib/go-atscfg/cachedotconfig.go b/lib/go-atscfg/cachedotconfig.go
index ba95e0e..7b23107 100644
--- a/lib/go-atscfg/cachedotconfig.go
+++ b/lib/go-atscfg/cachedotconfig.go
@@ -20,6 +20,7 @@ package atscfg
  */
 
 import (
+       "sort"
        "strings"
 
        "github.com/apache/trafficcontrol/lib/go-log"
@@ -27,6 +28,7 @@ import (
 )
 
 const ContentTypeCacheDotConfig = ContentTypeTextASCII
+const LineCommentCacheDotConfig = LineCommentHash
 
 type ProfileDS struct {
        Type       tc.DSType
@@ -60,10 +62,12 @@ func MakeCacheDotConfig(
                }
        }
 
-       text := ""
+       linesArr := []string{}
        for line, _ := range lines {
-               text += line
+               linesArr = append(linesArr, line)
        }
+       sort.Strings(linesArr)
+       text := strings.Join(linesArr, "")
        if text == "" {
                text = "\n" // If no params exist, don't send "not found," but 
an empty file. We know the profile exists.
        }
diff --git a/lib/go-atscfg/cacheurldotconfig.go 
b/lib/go-atscfg/cacheurldotconfig.go
index f6a077c..1f48110 100644
--- a/lib/go-atscfg/cacheurldotconfig.go
+++ b/lib/go-atscfg/cacheurldotconfig.go
@@ -27,6 +27,7 @@ import (
 )
 
 const ContentTypeCacheURLDotConfig = ContentTypeTextASCII
+const LineCommentCacheURLDotConfig = LineCommentHash
 
 type CacheURLDS struct {
        OrgServerFQDN string
diff --git a/lib/go-atscfg/chkconfig.go b/lib/go-atscfg/chkconfig.go
index 581ca38..9d870c9 100644
--- a/lib/go-atscfg/chkconfig.go
+++ b/lib/go-atscfg/chkconfig.go
@@ -21,6 +21,7 @@ package atscfg
 
 import (
        "encoding/json"
+       "sort"
 
        "github.com/apache/trafficcontrol/lib/go-log"
 )
@@ -28,12 +29,24 @@ import (
 const ChkconfigFileName = `chkconfig`
 const ChkconfigParamConfigFile = `chkconfig`
 const ContentTypeChkconfig = ContentTypeTextASCII
+const LineCommentChkconfig = LineCommentHash
 
 type ChkConfigEntry struct {
        Name string `json:"name"`
        Val  string `json:"value"`
 }
 
+type ChkConfigEntries []ChkConfigEntry
+
+func (e ChkConfigEntries) Len() int { return len(e) }
+func (e ChkConfigEntries) Less(i, j int) bool {
+       if e[i].Name != e[j].Name {
+               return e[i].Name < e[j].Name
+       }
+       return e[i].Val < e[j].Val
+}
+func (e ChkConfigEntries) Swap(i, j int) { e[i], e[j] = e[j], e[i] }
+
 // MakeChkconfig returns the 'chkconfig' ATS config file endpoint.
 // This is a JSON object, and should be served with an 'application/json' 
Content-Type.
 func MakeChkconfig(
@@ -46,6 +59,9 @@ func MakeChkconfig(
                        chkconfig = append(chkconfig, ChkConfigEntry{Name: 
name, Val: val})
                }
        }
+
+       sort.Sort(ChkConfigEntries(chkconfig))
+
        bts, err := json.Marshal(&chkconfig)
        if err != nil {
                // should never happen
diff --git a/lib/go-atscfg/dropqstringdotconfig.go 
b/lib/go-atscfg/dropqstringdotconfig.go
index 3c7c59b..68d9189 100644
--- a/lib/go-atscfg/dropqstringdotconfig.go
+++ b/lib/go-atscfg/dropqstringdotconfig.go
@@ -22,6 +22,7 @@ package atscfg
 const DropQStringDotConfigFileName = "drop_qstring.config"
 const DropQStringDotConfigParamName = "content"
 const ContentTypeDropQStringDotConfig = ContentTypeTextASCII
+const LineCommentDropQStringDotConfig = LineCommentHash
 
 func MakeDropQStringDotConfig(
        profileName string,
diff --git a/lib/go-atscfg/facts.go b/lib/go-atscfg/facts.go
index 70102ce..c7e6eb6 100644
--- a/lib/go-atscfg/facts.go
+++ b/lib/go-atscfg/facts.go
@@ -20,6 +20,7 @@ package atscfg
  */
 
 const ContentType12MFacts = ContentTypeTextASCII
+const LineComment12MFacts = LineCommentHash
 
 func Make12MFacts(
        profileName string,
diff --git a/lib/go-atscfg/headerrewritedotconfig.go 
b/lib/go-atscfg/headerrewritedotconfig.go
index 083ee91..34a96ae 100644
--- a/lib/go-atscfg/headerrewritedotconfig.go
+++ b/lib/go-atscfg/headerrewritedotconfig.go
@@ -31,6 +31,7 @@ import (
 
 const HeaderRewritePrefix = "hdr_rw_"
 const ContentTypeHeaderRewriteDotConfig = ContentTypeTextASCII
+const LineCommentHeaderRewriteDotConfig = LineCommentHash
 
 const MaxOriginConnectionsNoMax = 0 // 0 indicates no limit on origin 
connections
 
diff --git a/lib/go-atscfg/hostingdotconfig.go 
b/lib/go-atscfg/hostingdotconfig.go
index f3f53bc..5df86e9 100644
--- a/lib/go-atscfg/hostingdotconfig.go
+++ b/lib/go-atscfg/hostingdotconfig.go
@@ -20,6 +20,7 @@ package atscfg
  */
 
 import (
+       "sort"
        "strconv"
        "strings"
 
@@ -29,6 +30,7 @@ import (
 const HostingConfigFileName = `hosting.config`
 const HostingConfigParamConfigFile = `storage.config`
 const ContentTypeHostingDotConfig = ContentTypeTextASCII
+const LineCommentHostingDotConfig = LineCommentHash
 
 const ParamDrivePrefix = "Drive_Prefix"
 const ParamRAMDrivePrefix = "RAM_Drive_Prefix"
@@ -42,6 +44,7 @@ func MakeHostingDotConfig(
 ) string {
        text := GenericHeaderComment(string(serverName), toToolName, toURL)
 
+       lines := []string{}
        if _, ok := params[ParamRAMDrivePrefix]; ok {
                nextVolume := 1
                if _, ok := params[ParamDrivePrefix]; ok {
@@ -60,10 +63,14 @@ func MakeHostingDotConfig(
                        seenOrigins[origin] = struct{}{}
                        origin = strings.TrimPrefix(origin, `http://`)
                        origin = strings.TrimPrefix(origin, `https://`)
-                       text += `hostname=` + origin + ` volume=` + 
strconv.Itoa(ramVolume) + "\n"
+                       lines = append(lines, `hostname=`+origin+` 
volume=`+strconv.Itoa(ramVolume)+"\n")
                }
        }
        diskVolume := 1 // note this will actually be the RAM 
(RAM_Drive_Prefix) volume if there is no Drive_Prefix parameter.
-       text += `hostname=*   volume=` + strconv.Itoa(diskVolume) + "\n"
+
+       lines = append(lines, `hostname=*   
volume=`+strconv.Itoa(diskVolume)+"\n")
+
+       sort.Strings(lines)
+       text += strings.Join(lines, "")
        return text
 }
diff --git a/lib/go-atscfg/ipallowdotconfig.go 
b/lib/go-atscfg/ipallowdotconfig.go
index 4593536..b851a51 100644
--- a/lib/go-atscfg/ipallowdotconfig.go
+++ b/lib/go-atscfg/ipallowdotconfig.go
@@ -21,6 +21,7 @@ package atscfg
 
 import (
        "net"
+       "sort"
        "strconv"
        "strings"
 
@@ -31,6 +32,7 @@ import (
 
 const IPAllowConfigFileName = `ip_allow.config`
 const ContentTypeIPAllowDotConfig = ContentTypeTextASCII
+const LineCommentIPAllowDotConfig = LineCommentHash
 
 type IPAllowData struct {
        Src    string
@@ -38,6 +40,20 @@ type IPAllowData struct {
        Method string
 }
 
+type IPAllowDatas []IPAllowData
+
+func (is IPAllowDatas) Len() int      { return len(is) }
+func (is IPAllowDatas) Swap(i, j int) { is[i], is[j] = is[j], is[i] }
+func (is IPAllowDatas) Less(i, j int) bool {
+       if is[i].Src != is[j].Src {
+               return is[i].Src < is[j].Src
+       }
+       if is[i].Action != is[j].Action {
+               return is[i].Action < is[j].Action
+       }
+       return is[i].Method < is[j].Method
+}
+
 const ParamPurgeAllowIP = "purge_allow_ip"
 const ParamCoalesceMaskLenV4 = "coalesce_masklen_v4"
 const ParamCoalesceNumberV4 = "coalesce_number_v4"
@@ -233,6 +249,9 @@ func MakeIPAllowDotConfig(
                        Method: MethodAll,
                })
 
+               // order matters, so sort before adding the denys
+               sort.Sort(IPAllowDatas(ipAllowData))
+
                // end with a deny
                ipAllowData = append(ipAllowData, IPAllowData{
                        Src:    `0.0.0.0-255.255.255.255`,
diff --git a/lib/go-atscfg/loggingdotconfig.go 
b/lib/go-atscfg/loggingdotconfig.go
index fdb24fc..0d9f375 100644
--- a/lib/go-atscfg/loggingdotconfig.go
+++ b/lib/go-atscfg/loggingdotconfig.go
@@ -30,6 +30,7 @@ const MaxLogObjects = 10
 
 const LoggingFileName = "logging.config"
 const ContentTypeLoggingDotConfig = ContentTypeTextASCII
+const LineCommentLoggingDotConfig = LineCommentHash
 
 // MakeStorageDotConfig creates storage.config for a given ATS Profile.
 // The paramData is the map of parameter names to values, for all parameters 
assigned to the given profile, with the config_file "storage.config".
diff --git a/lib/go-atscfg/loggingdotyaml.go b/lib/go-atscfg/loggingdotyaml.go
index 84851de..ab5fc00 100644
--- a/lib/go-atscfg/loggingdotyaml.go
+++ b/lib/go-atscfg/loggingdotyaml.go
@@ -28,6 +28,7 @@ import (
 
 const LoggingYAMLFileName = "logging.yaml"
 const ContentTypeLoggingDotYAML = "application/yaml; charset=us-ascii" // Note 
YAML has no IANA standard mime type. This is one of several common usages, and 
is likely to be the standardized value. If you're reading this, please check 
IANA to see if YAML has been added, and change this to the IANA definition if 
so. Also note we include 'charset=us-ascii' because YAML is commonly UTF-8, but 
ATS is likely to be unable to handle UTF.
+const LineCommentLoggingDotYAML = LineCommentHash
 
 func MakeLoggingDotYAML(
        profileName string,
diff --git a/lib/go-atscfg/logsdotxml.go b/lib/go-atscfg/logsdotxml.go
index 991b411..72e6525 100644
--- a/lib/go-atscfg/logsdotxml.go
+++ b/lib/go-atscfg/logsdotxml.go
@@ -27,12 +27,18 @@ import (
 const LogsXMLFileName = "logs_xml.config"
 const ContentTypeLogsDotXML = `text/xml`
 
+const LineCommentLogsDotXML = `<!--`
+
 func MakeLogsXMLDotConfig(
        profileName string,
        paramData map[string]string, // GetProfileParamData(tx, profile.ID, 
LoggingYAMLFileName)
        toToolName string, // tm.toolname global parameter (TODO: cache itself?)
        toURL string, // tm.url global parameter (TODO: cache itself?)
 ) string {
+
+       // Note LineCommentLogsDotXML must be a single-line comment!
+       // But this file doesn't have a single-line format, so we use <!-- for 
the header and promise it's on a single line
+       // Note! if this file is ever changed to have multi-line comments, 
LineCommentLogsDotXML will have to be changed to the empty string.
        hdrComment := GenericHeaderComment(profileName, toToolName, toURL)
        hdrComment = strings.Replace(hdrComment, `# `, ``, -1)
        hdrComment = strings.Replace(hdrComment, "\n", ``, -1)
diff --git a/lib/go-atscfg/packages.go b/lib/go-atscfg/packages.go
index b675072..f148d8a 100644
--- a/lib/go-atscfg/packages.go
+++ b/lib/go-atscfg/packages.go
@@ -21,6 +21,7 @@ package atscfg
 
 import (
        "encoding/json"
+       "sort"
 
        "github.com/apache/trafficcontrol/lib/go-log"
 )
@@ -29,12 +30,24 @@ const PackagesFileName = `packages`
 const PackagesParamConfigFile = `package`
 
 const ContentTypePackages = ContentTypeTextASCII
+const LineCommentPackages = ""
 
 type Package struct {
        Name    string `json:"name"`
        Version string `json:"version"`
 }
 
+type Packages []Package
+
+func (ps Packages) Len() int { return len(ps) }
+func (ps Packages) Less(i, j int) bool {
+       if ps[i].Name != ps[j].Name {
+               return ps[i].Name < ps[j].Name
+       }
+       return ps[i].Version < ps[j].Version
+}
+func (ps Packages) Swap(i, j int) { ps[i], ps[j] = ps[j], ps[i] }
+
 // MakePackages returns the 'packages' ATS config file endpoint.
 // This is a JSON object, and should be served with an 'application/json' 
Content-Type.
 func MakePackages(
@@ -46,6 +59,7 @@ func MakePackages(
                        packages = append(packages, Package{Name: name, 
Version: version})
                }
        }
+       sort.Sort(Packages(packages))
        bts, err := json.Marshal(&packages)
        if err != nil {
                // should never happen
diff --git a/lib/go-atscfg/parentdotconfig.go b/lib/go-atscfg/parentdotconfig.go
index a80a7e2..621cf3e 100644
--- a/lib/go-atscfg/parentdotconfig.go
+++ b/lib/go-atscfg/parentdotconfig.go
@@ -32,6 +32,7 @@ import (
 )
 
 const ContentTypeParentDotConfig = ContentTypeTextASCII
+const LineCommentParentDotConfig = LineCommentHash
 
 const ParentConfigParamQStringHandling = "psel.qstring_handling"
 const ParentConfigParamMSOAlgorithm = "mso.algorithm"
@@ -170,8 +171,7 @@ func MakeParentDotConfig(
        serverParams map[string]string, // 
getParentConfigServerProfileParams(serverID)
        parentInfos map[OriginHost][]ParentInfo, // getParentInfo(profileID, 
parentCachegroupID, secondaryParentCachegroupID)
 ) string {
-
-       // parentInfos := makeParentInfo(serverInfo)
+       sort.Sort(ParentConfigDSTopLevelSortByName(parentConfigDSes))
 
        nameVersionStr := GetNameVersionStringFromToolNameAndURL(toToolName, 
toURL)
        hdr := HeaderCommentWithTOVersionStr(serverInfo.HostName, 
nameVersionStr)
@@ -257,8 +257,6 @@ func MakeParentDotConfig(
                roundRobin := `round_robin=consistent_hash`
                goDirect := `go_direct=false`
 
-               sort.Sort(ParentConfigDSTopLevelSortByName(parentConfigDSes))
-
                for _, ds := range parentConfigDSes {
                        parents, secondaryParents := getParentStrs(ds, 
parentInfos[DeliveryServicesAllParentsKey], atsMajorVer)
 
diff --git a/lib/go-atscfg/plugindotconfig.go b/lib/go-atscfg/plugindotconfig.go
index 5a9eb5a..b4e45cf 100644
--- a/lib/go-atscfg/plugindotconfig.go
+++ b/lib/go-atscfg/plugindotconfig.go
@@ -22,6 +22,7 @@ package atscfg
 const PluginSeparator = " "
 const PluginFileName = "plugin.config"
 const ContentTypePluginDotConfig = ContentTypeTextASCII
+const LineCommentPluginDotConfig = LineCommentHash
 
 func MakePluginDotConfig(
        profileName string,
diff --git a/lib/go-atscfg/recordsdotconfig.go 
b/lib/go-atscfg/recordsdotconfig.go
index 18aa3f8..4c50400 100644
--- a/lib/go-atscfg/recordsdotconfig.go
+++ b/lib/go-atscfg/recordsdotconfig.go
@@ -26,6 +26,7 @@ import (
 const RecordsSeparator = " "
 const RecordsFileName = "records.config"
 const ContentTypeRecordsDotConfig = ContentTypeTextASCII
+const LineCommentRecordsDotConfig = LineCommentHash
 
 func MakeRecordsDotConfig(
        profileName string,
diff --git a/lib/go-atscfg/regexremapdotconfig.go 
b/lib/go-atscfg/regexremapdotconfig.go
index f726793..76447c1 100644
--- a/lib/go-atscfg/regexremapdotconfig.go
+++ b/lib/go-atscfg/regexremapdotconfig.go
@@ -27,6 +27,7 @@ import (
 )
 
 const ContentTypeRegexRemapDotConfig = ContentTypeTextASCII
+const LineCommentRegexRemapDotConfig = LineCommentHash
 
 type CDNDS struct {
        OrgServerFQDN string
diff --git a/lib/go-atscfg/regexrevalidatedotconfig.go 
b/lib/go-atscfg/regexrevalidatedotconfig.go
index ad614f9..a3fc9c1 100644
--- a/lib/go-atscfg/regexrevalidatedotconfig.go
+++ b/lib/go-atscfg/regexrevalidatedotconfig.go
@@ -41,6 +41,7 @@ const JobKeywordPurge = "PURGE"
 const RegexRevalidateMinTTL = time.Hour
 
 const ContentTypeRegexRevalidateDotConfig = ContentTypeTextASCII
+const LineCommentRegexRevalidateDotConfig = LineCommentHash
 
 type Job struct {
        AssetURL string
@@ -71,6 +72,7 @@ func MakeRegexRevalidateDotConfig(
 
        maxDays := DefaultMaxRevalDurationDays
        if maxDaysStrs := params[RegexRevalidateMaxRevalDurationDaysParamName]; 
len(maxDaysStrs) > 0 {
+               sort.Strings(maxDaysStrs)
                if maxDays, err = strconv.Atoi(maxDaysStrs[0]); err != nil { // 
just use the first, if there were multiple params
                        log.Warnln("making regex revalidate config: max days 
param '" + maxDaysStrs[0] + "' is not an integer, using default value!")
                        maxDays = DefaultMaxRevalDurationDays
diff --git a/lib/go-atscfg/remapdotconfig.go b/lib/go-atscfg/remapdotconfig.go
index 7b91686..8532a6f 100644
--- a/lib/go-atscfg/remapdotconfig.go
+++ b/lib/go-atscfg/remapdotconfig.go
@@ -32,6 +32,7 @@ import (
 const CacheURLParameterConfigFile = "cacheurl.config"
 const CacheKeyParameterConfigFile = "cachekey.config"
 const ContentTypeRemapDotConfig = ContentTypeTextASCII
+const LineCommentRemapDotConfig = LineCommentHash
 
 type RemapConfigDSData struct {
        ID                       int
diff --git a/lib/go-atscfg/servercachedotconfig.go 
b/lib/go-atscfg/servercachedotconfig.go
index 9d1d29c..f436477 100644
--- a/lib/go-atscfg/servercachedotconfig.go
+++ b/lib/go-atscfg/servercachedotconfig.go
@@ -20,6 +20,7 @@ package atscfg
  */
 
 import (
+       "sort"
        "strconv"
        "strings"
 
@@ -39,6 +40,8 @@ func MakeServerCacheDotConfig(
 ) string {
        text := GenericHeaderComment(string(serverName), toToolName, toURL)
 
+       lines := []string{}
+
        seenOrigins := map[string]struct{}{}
        for _, ds := range dses {
                if ds.Type != tc.DSTypeHTTPNoCache {
@@ -51,12 +54,13 @@ func MakeServerCacheDotConfig(
 
                originFQDN, originPort := GetOriginFQDNAndPort(ds.OrgServerFQDN)
                if originPort != nil {
-                       text += `dest_domain=` + originFQDN + ` port=` + 
strconv.Itoa(*originPort) + ` scheme=http action=never-cache` + "\n"
+                       lines = append(lines, `dest_domain=`+originFQDN+` 
port=`+strconv.Itoa(*originPort)+` scheme=http action=never-cache`+"\n")
                } else {
-                       text += `dest_domain=` + originFQDN + ` scheme=http 
action=never-cache` + "\n"
+                       lines = append(lines, `dest_domain=`+originFQDN+` 
scheme=http action=never-cache`+"\n")
                }
        }
-       return text
+       sort.Strings(lines)
+       return text + strings.Join(lines, "")
 }
 
 // TODO unit test
diff --git a/lib/go-atscfg/serverunknown.go b/lib/go-atscfg/serverunknown.go
index 68c3732..309c4f0 100644
--- a/lib/go-atscfg/serverunknown.go
+++ b/lib/go-atscfg/serverunknown.go
@@ -83,3 +83,21 @@ func SortParams(params map[string][]string) []Param {
        sort.Sort(Params(sortedParams))
        return sortedParams
 }
+
+// GetServerUnknownConfigCommentType takes the same data as MakeUnknownConfig 
and returns the comment type for that config.
+// In particular, it returns # unless there is a 'header' parameter, in which 
case it returns an empty string.
+// Wwe don't actually know that the first characters of a custom header are a 
comment, or how many characters it might be.
+func GetServerUnknownConfigCommentType(
+       serverName tc.CacheName,
+       serverDomain string,
+       toToolName string,
+       toURL string,
+       params map[string][]string,
+) string {
+       for name, _ := range params {
+               if name == "header" {
+                       return ""
+               }
+       }
+       return LineCommentHash
+}
diff --git a/lib/go-atscfg/setdscpdotconfig.go 
b/lib/go-atscfg/setdscpdotconfig.go
index 3f2ee3c..a13576a 100644
--- a/lib/go-atscfg/setdscpdotconfig.go
+++ b/lib/go-atscfg/setdscpdotconfig.go
@@ -26,6 +26,7 @@ import (
 )
 
 const ContentTypeSetDSCPDotConfig = ContentTypeTextASCII
+const LineCommentSetDSCPDotConfig = LineCommentHash
 
 func MakeSetDSCPDotConfig(
        cdnName tc.CDNName,
diff --git a/lib/go-atscfg/sslmulticertdotconfig.go 
b/lib/go-atscfg/sslmulticertdotconfig.go
index 64cd48b..d9b0139 100644
--- a/lib/go-atscfg/sslmulticertdotconfig.go
+++ b/lib/go-atscfg/sslmulticertdotconfig.go
@@ -20,6 +20,7 @@ package atscfg
  */
 
 import (
+       "sort"
        "strings"
 
        "github.com/apache/trafficcontrol/lib/go-log"
@@ -27,6 +28,7 @@ import (
 )
 
 const ContentTypeSSLMultiCertDotConfig = ContentTypeTextASCII
+const LineCommentSSLMultiCertDotConfig = LineCommentHash
 const SSLMultiCertConfigFileName = `ssl_multicert.config`
 
 type SSLMultiCertDS struct {
@@ -58,15 +60,17 @@ func MakeSSLMultiCertDotConfig(
        toURL string, // tm.url global parameter (TODO: cache itself?)
        dses map[tc.DeliveryServiceName]SSLMultiCertDS,
 ) string {
-       text := GenericHeaderComment(string(cdnName), toToolName, toURL)
+       hdr := GenericHeaderComment(string(cdnName), toToolName, toURL)
 
        dses = GetSSLMultiCertDotConfigDeliveryServices(dses)
 
+       lines := []string{}
        for dsName, ds := range dses {
                cerName, keyName := 
GetSSLMultiCertDotConfigCertAndKeyName(dsName, ds)
-               text += `ssl_cert_name=` + cerName + "\t" + ` ssl_key_name=` + 
keyName + "\n"
+               lines = append(lines, `ssl_cert_name=`+cerName+"\t"+` 
ssl_key_name=`+keyName+"\n")
        }
-       return text
+       sort.Strings(lines)
+       return hdr + strings.Join(lines, "")
 }
 
 // GetSSLMultiCertDotConfigCertAndKeyName returns the cert file name and key 
file name for the given delivery service.
diff --git a/lib/go-atscfg/storagedotconfig.go 
b/lib/go-atscfg/storagedotconfig.go
index a78cdff..50123b1 100644
--- a/lib/go-atscfg/storagedotconfig.go
+++ b/lib/go-atscfg/storagedotconfig.go
@@ -27,6 +27,7 @@ import (
 )
 
 const ContentTypeStorageDotConfig = ContentTypeTextASCII
+const LineCommentStorageDotConfig = LineCommentHash
 
 // MakeStorageDotConfig creates storage.config for a given ATS Profile.
 // The paramData is the map of parameter names to values, for all parameters 
assigned to the given profile, with the config_file "storage.config".
diff --git a/lib/go-atscfg/sysctldotconf.go b/lib/go-atscfg/sysctldotconf.go
index ba3c144..cbc51de 100644
--- a/lib/go-atscfg/sysctldotconf.go
+++ b/lib/go-atscfg/sysctldotconf.go
@@ -22,6 +22,7 @@ package atscfg
 const SysctlSeparator = " = "
 const SysctlFileName = "sysctl.conf"
 const ContentTypeSysctlDotConf = ContentTypeTextASCII
+const LineCommentSysctlDotConf = LineCommentHash
 
 func MakeSysCtlDotConf(
        profileName string,
diff --git a/lib/go-atscfg/unknownconfig.go b/lib/go-atscfg/unknownconfig.go
index 255d3b1..1f4742e 100644
--- a/lib/go-atscfg/unknownconfig.go
+++ b/lib/go-atscfg/unknownconfig.go
@@ -20,6 +20,7 @@ package atscfg
  */
 
 import (
+       "sort"
        "strings"
 )
 
@@ -33,7 +34,7 @@ func MakeUnknownConfig(
 ) string {
        hdr := GenericHeaderComment(profileName, toToolName, toURL)
 
-       text := ""
+       lines := []string{}
        for paramName, paramVal := range paramData {
                if paramName == "header" {
                        if paramVal == "none" {
@@ -42,9 +43,28 @@ func MakeUnknownConfig(
                                hdr = paramVal + "\n"
                        }
                } else {
-                       text += paramVal + "\n"
+                       lines = append(lines, paramVal+"\n")
                }
        }
+       sort.Strings(lines)
+       text := strings.Join(lines, "")
        text = strings.Replace(text, "__RETURN__", "\n", -1)
        return hdr + text
 }
+
+// GetUnknownConfigCommentType takes the same data as MakeUnknownConfig and 
returns the comment type for that config.
+// In particular, it returns # unless there is a 'header' parameter, in which 
case it returns an empty string.
+// Wwe don't actually know that the first characters of a custom header are a 
comment, or how many characters it might be.
+func GetUnknownConfigCommentType(
+       profileName string,
+       paramData map[string]string,
+       toToolName string,
+       toURL string,
+) string {
+       for paramName, _ := range paramData {
+               if paramName == "header" {
+                       return ""
+               }
+       }
+       return LineCommentHash
+}
diff --git a/lib/go-atscfg/urisigningconfig.go 
b/lib/go-atscfg/urisigningconfig.go
index a9db57e..a5ff635 100644
--- a/lib/go-atscfg/urisigningconfig.go
+++ b/lib/go-atscfg/urisigningconfig.go
@@ -20,6 +20,7 @@ package atscfg
  */
 
 const ContentTypeURISigningDotConfig = `application/json; charset=us-ascii`
+const LineCommentURISigningDotConfig = ""
 
 func MakeURISigningConfig(
        uriSigningKeysBts []byte,
diff --git a/lib/go-atscfg/urlsigconfig.go b/lib/go-atscfg/urlsigconfig.go
index 938414a..0193ae2 100644
--- a/lib/go-atscfg/urlsigconfig.go
+++ b/lib/go-atscfg/urlsigconfig.go
@@ -20,11 +20,15 @@ package atscfg
  */
 
 import (
+       "sort"
        "strings"
 
        "github.com/apache/trafficcontrol/lib/go-tc"
 )
 
+const ContentTypeURLSig = ContentTypeTextASCII
+const LineCommentURLSig = LineCommentHash
+
 func MakeURLSigConfig(
        profileName string,
        urlSigKeys tc.URLSigKeys,
@@ -37,14 +41,22 @@ func MakeURLSigConfig(
        sep := " = "
 
        text := hdr
+
+       paramLines := []string{}
        for paramName, paramVal := range paramData {
                if len(urlSigKeys) == 0 || !strings.HasPrefix(paramName, "key") 
{
-                       text += paramName + sep + paramVal + "\n"
+                       paramLines = append(paramLines, 
paramName+sep+paramVal+"\n")
                }
        }
+       sort.Strings(paramLines)
+       text += strings.Join(paramLines, "")
 
+       keyLines := []string{}
        for key, val := range urlSigKeys {
-               text += key + sep + val + "\n"
+               keyLines = append(keyLines, key+sep+val+"\n")
        }
+       sort.Strings(keyLines)
+       text += strings.Join(keyLines, "")
+
        return text
 }
diff --git a/lib/go-atscfg/volumedotconfig.go b/lib/go-atscfg/volumedotconfig.go
index af92fb4..bef49e9 100644
--- a/lib/go-atscfg/volumedotconfig.go
+++ b/lib/go-atscfg/volumedotconfig.go
@@ -24,6 +24,7 @@ import (
 )
 
 const ContentTypeVolumeDotConfig = ContentTypeTextASCII
+const LineCommentVolumeDotConfig = LineCommentHash
 
 // MakeVolumeDotConfig creates volume.config for a given ATS Profile.
 // The paramData is the map of parameter names to values, for all parameters 
assigned to the given profile, with the config_file "storage.config".
diff --git a/traffic_ops/ort/atstccfg/atstccfg.go 
b/traffic_ops/ort/atstccfg/atstccfg.go
index ee9ab87..0a77c62 100644
--- a/traffic_ops/ort/atstccfg/atstccfg.go
+++ b/traffic_ops/ort/atstccfg/atstccfg.go
@@ -50,6 +50,7 @@ package main
 import (
        "fmt"
        "os"
+       "sort"
        "strings"
 
        "github.com/apache/trafficcontrol/lib/go-log"
@@ -108,7 +109,7 @@ func main() {
                os.Exit(config.ExitCodeErrGeneric)
        }
 
-       configs, err := cfgfile.GetAllConfigs(tccfg, toData)
+       configs, err := cfgfile.GetAllConfigs(toData, tccfg.RevalOnly)
        if err != nil {
                log.Errorln("Getting config for'" + cfg.CacheHostName + "': " + 
err.Error())
                os.Exit(config.ExitCodeErrGeneric)
@@ -117,6 +118,8 @@ func main() {
        modifyFilesData := plugin.ModifyFilesData{Cfg: tccfg, TOData: toData, 
Files: configs}
        configs = plugins.ModifyFiles(modifyFilesData)
 
+       sort.Sort(config.ATSConfigFiles(configs))
+
        if err := cfgfile.WriteConfigs(configs, os.Stdout); err != nil {
                log.Errorln("Writing configs for '" + cfg.CacheHostName + "': " 
+ err.Error())
                os.Exit(config.ExitCodeErrGeneric)
diff --git a/traffic_ops/ort/atstccfg/cfgfile/all.go 
b/traffic_ops/ort/atstccfg/cfgfile/all.go
index d006a88..edf3be1 100644
--- a/traffic_ops/ort/atstccfg/cfgfile/all.go
+++ b/traffic_ops/ort/atstccfg/cfgfile/all.go
@@ -36,7 +36,7 @@ import (
 )
 
 // GetAllConfigs gets all config files for cfg.CacheHostName.
-func GetAllConfigs(cfg config.TCCfg, toData *config.TOData) 
([]config.ATSConfigFile, error) {
+func GetAllConfigs(toData *config.TOData, revalOnly bool) 
([]config.ATSConfigFile, error) {
        meta, err := GetMeta(toData)
        if err != nil {
                return nil, errors.New("creating meta: " + err.Error())
@@ -45,10 +45,10 @@ func GetAllConfigs(cfg config.TCCfg, toData *config.TOData) 
([]config.ATSConfigF
        hasSSLMultiCertConfig := false
        configs := []config.ATSConfigFile{}
        for _, fi := range meta.ConfigFiles {
-               if cfg.RevalOnly && fi.FileNameOnDisk != 
atscfg.RegexRevalidateFileName {
+               if revalOnly && fi.FileNameOnDisk != 
atscfg.RegexRevalidateFileName {
                        continue
                }
-               txt, contentType, err := GetConfigFile(toData, fi)
+               txt, contentType, lineComment, err := GetConfigFile(toData, fi)
                if err != nil {
                        return nil, errors.New("getting config file '" + 
fi.APIURI + "': " + err.Error())
                }
@@ -56,7 +56,7 @@ func GetAllConfigs(cfg config.TCCfg, toData *config.TOData) 
([]config.ATSConfigF
                        hasSSLMultiCertConfig = true
                }
                txt = PreprocessConfigFile(toData.Server, txt)
-               configs = append(configs, 
config.ATSConfigFile{ATSConfigMetaDataConfigFile: fi, Text: txt, ContentType: 
contentType})
+               configs = append(configs, 
config.ATSConfigFile{ATSConfigMetaDataConfigFile: fi, Text: txt, ContentType: 
contentType, LineComment: lineComment})
        }
 
        if hasSSLMultiCertConfig {
@@ -71,13 +71,15 @@ func GetAllConfigs(cfg config.TCCfg, toData *config.TOData) 
([]config.ATSConfigF
 }
 
 const HdrConfigFilePath = "Path"
+const HdrLineComment = "Line-Comment"
 
 // WriteConfigs writes the given configs as a RFC2046ยง5.1 MIME multipart/mixed 
message.
 func WriteConfigs(configs []config.ATSConfigFile, output io.Writer) error {
        w := multipart.NewWriter(output)
 
        // Create a unique boundary. Because we're using a text encoding, we 
need to make sure the boundary text doesn't occur in any body.
-       boundary := w.Boundary()
+       // Always start with the same random UUID, so generating twice diffs 
the same (except in the unlikely chance this string is in a config somewhere).
+       boundary := `dc5p7zOLNkyTzdcZSme6tg` // random UUID
        randSet := `abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ`
        for _, cfg := range configs {
                for strings.Contains(cfg.Text, boundary) {
@@ -93,6 +95,7 @@ func WriteConfigs(configs []config.ATSConfigFile, output 
io.Writer) error {
        for _, cfg := range configs {
                hdr := map[string][]string{
                        rfc.ContentType:   {cfg.ContentType},
+                       HdrLineComment:    {cfg.LineComment},
                        HdrConfigFilePath: {filepath.Join(cfg.Location, 
cfg.FileNameOnDisk)},
                }
                partW, err := w.CreatePart(hdr)
diff --git a/traffic_ops/ort/atstccfg/cfgfile/astatsdotconfig.go 
b/traffic_ops/ort/atstccfg/cfgfile/astatsdotconfig.go
index 32d09b7..c90d5c4 100644
--- a/traffic_ops/ort/atstccfg/cfgfile/astatsdotconfig.go
+++ b/traffic_ops/ort/atstccfg/cfgfile/astatsdotconfig.go
@@ -21,10 +21,11 @@ package cfgfile
 
 import (
        "github.com/apache/trafficcontrol/lib/go-atscfg"
+       "github.com/apache/trafficcontrol/lib/go-log"
        "github.com/apache/trafficcontrol/traffic_ops/ort/atstccfg/config"
 )
 
-func GetConfigFileProfileAstatsDotConfig(toData *config.TOData) (string, 
string, error) {
+func GetConfigFileProfileAstatsDotConfig(toData *config.TOData) (string, 
string, string, error) {
        paramData := map[string]string{}
        // TODO add configFile query param to profile/parameters endpoint, to 
only get needed data
        for _, param := range toData.ServerParams {
@@ -34,8 +35,16 @@ func GetConfigFileProfileAstatsDotConfig(toData 
*config.TOData) (string, string,
                if param.Name == "location" {
                        continue
                }
+               if val, ok := paramData[param.Name]; ok {
+                       if val < param.Value {
+                               log.Errorln("data error: making astats.config: 
parameter '" + param.Name + "' had multiple values, ignoring '" + param.Value + 
"'")
+                               continue
+                       } else {
+                               log.Errorln("data error: making astats.config: 
parameter '" + param.Name + "' had multiple values, ignoring '" + val + "'")
+                       }
+               }
                paramData[param.Name] = param.Value
        }
 
-       return atscfg.MakeAStatsDotConfig(toData.Server.Profile, paramData, 
toData.TOToolName, toData.TOURL), atscfg.ContentTypeAstatsDotConfig, nil
+       return atscfg.MakeAStatsDotConfig(toData.Server.Profile, paramData, 
toData.TOToolName, toData.TOURL), atscfg.ContentTypeAstatsDotConfig, 
atscfg.LineCommentAstatsDotConfig, nil
 }
diff --git a/traffic_ops/ort/atstccfg/cfgfile/atsdotrules.go 
b/traffic_ops/ort/atstccfg/cfgfile/atsdotrules.go
index 40edddf..ee51c24 100644
--- a/traffic_ops/ort/atstccfg/cfgfile/atsdotrules.go
+++ b/traffic_ops/ort/atstccfg/cfgfile/atsdotrules.go
@@ -21,12 +21,13 @@ package cfgfile
 
 import (
        "github.com/apache/trafficcontrol/lib/go-atscfg"
+       "github.com/apache/trafficcontrol/lib/go-log"
        "github.com/apache/trafficcontrol/traffic_ops/ort/atstccfg/config"
 )
 
 const ATSDotRulesFileName = StorageFileName
 
-func GetConfigFileProfileATSDotRules(toData *config.TOData) (string, string, 
error) {
+func GetConfigFileProfileATSDotRules(toData *config.TOData) (string, string, 
string, error) {
        paramData := map[string]string{}
        // TODO add configFile query param to profile/parameters endpoint, to 
only get needed data
        for _, param := range toData.ServerParams {
@@ -36,7 +37,15 @@ func GetConfigFileProfileATSDotRules(toData *config.TOData) 
(string, string, err
                if param.Name == "location" {
                        continue
                }
+               if val, ok := paramData[param.Name]; ok {
+                       if val < param.Value {
+                               log.Errorln("data error: making ats.rules: 
parameter '" + param.Name + "' had multiple values, ignoring '" + param.Value + 
"'")
+                               continue
+                       } else {
+                               log.Errorln("data error: making ats.rules: 
parameter '" + param.Name + "' had multiple values, ignoring '" + val + "'")
+                       }
+               }
                paramData[param.Name] = param.Value
        }
-       return atscfg.MakeATSDotRules(toData.Server.Profile, paramData, 
toData.TOToolName, toData.TOURL), atscfg.ContentTypeATSDotRules, nil
+       return atscfg.MakeATSDotRules(toData.Server.Profile, paramData, 
toData.TOToolName, toData.TOURL), atscfg.ContentTypeATSDotRules, 
atscfg.LineCommentATSDotRules, nil
 }
diff --git a/traffic_ops/ort/atstccfg/cfgfile/bgfetchdotconfig.go 
b/traffic_ops/ort/atstccfg/cfgfile/bgfetchdotconfig.go
index a9e56c7..d0de192 100644
--- a/traffic_ops/ort/atstccfg/cfgfile/bgfetchdotconfig.go
+++ b/traffic_ops/ort/atstccfg/cfgfile/bgfetchdotconfig.go
@@ -25,6 +25,6 @@ import (
        "github.com/apache/trafficcontrol/traffic_ops/ort/atstccfg/config"
 )
 
-func GetConfigFileCDNBGFetchDotConfig(toData *config.TOData) (string, string, 
error) {
-       return atscfg.MakeBGFetchDotConfig(tc.CDNName(toData.Server.CDNName), 
toData.TOToolName, toData.TOURL), atscfg.ContentTypeBGFetchDotConfig, nil
+func GetConfigFileCDNBGFetchDotConfig(toData *config.TOData) (string, string, 
string, error) {
+       return atscfg.MakeBGFetchDotConfig(tc.CDNName(toData.Server.CDNName), 
toData.TOToolName, toData.TOURL), atscfg.ContentTypeBGFetchDotConfig, 
atscfg.LineCommentBGFetchDotConfig, nil
 }
diff --git a/traffic_ops/ort/atstccfg/cfgfile/cachedotconfig.go 
b/traffic_ops/ort/atstccfg/cfgfile/cachedotconfig.go
index 7c27447..71fb566 100644
--- a/traffic_ops/ort/atstccfg/cfgfile/cachedotconfig.go
+++ b/traffic_ops/ort/atstccfg/cfgfile/cachedotconfig.go
@@ -25,7 +25,7 @@ import (
        "github.com/apache/trafficcontrol/traffic_ops/ort/atstccfg/config"
 )
 
-func GetConfigFileProfileCacheDotConfig(toData *config.TOData) (string, 
string, error) {
+func GetConfigFileProfileCacheDotConfig(toData *config.TOData) (string, 
string, string, error) {
        profileServerIDsMap := map[int]struct{}{}
        for _, sv := range toData.Servers {
                if sv.Profile != toData.Server.Profile {
@@ -65,5 +65,5 @@ func GetConfigFileProfileCacheDotConfig(toData 
*config.TOData) (string, string,
                profileDSes = append(profileDSes, atscfg.ProfileDS{Type: 
*ds.Type, OriginFQDN: &origin})
        }
 
-       return atscfg.MakeCacheDotConfig(toData.Server.Profile, profileDSes, 
toData.TOToolName, toData.TOURL), atscfg.ContentTypeCacheDotConfig, nil
+       return atscfg.MakeCacheDotConfig(toData.Server.Profile, profileDSes, 
toData.TOToolName, toData.TOURL), atscfg.ContentTypeCacheDotConfig, 
atscfg.LineCommentCacheDotConfig, nil
 }
diff --git a/traffic_ops/ort/atstccfg/cfgfile/cacheurldotconfig.go 
b/traffic_ops/ort/atstccfg/cfgfile/cacheurldotconfig.go
index 11237d6..ae60a85 100644
--- a/traffic_ops/ort/atstccfg/cfgfile/cacheurldotconfig.go
+++ b/traffic_ops/ort/atstccfg/cfgfile/cacheurldotconfig.go
@@ -25,7 +25,7 @@ import (
        "github.com/apache/trafficcontrol/traffic_ops/ort/atstccfg/config"
 )
 
-func GetConfigFileCDNCacheURL(toData *config.TOData, fileName string) (string, 
string, error) {
+func GetConfigFileCDNCacheURL(toData *config.TOData, fileName string) (string, 
string, string, error) {
        dsIDs := map[int]struct{}{}
        for _, ds := range toData.DeliveryServices {
                if ds.ID != nil {
@@ -60,9 +60,9 @@ func GetConfigFileCDNCacheURL(toData *config.TOData, fileName 
string) (string, s
 
        cfgDSes := atscfg.DeliveryServicesToCacheURLDSes(dsesWithServers)
 
-       return atscfg.MakeCacheURLDotConfig(tc.CDNName(toData.Server.CDNName), 
toData.TOToolName, toData.TOURL, fileName, cfgDSes), 
atscfg.ContentTypeCacheURLDotConfig, nil
+       return atscfg.MakeCacheURLDotConfig(tc.CDNName(toData.Server.CDNName), 
toData.TOToolName, toData.TOURL, fileName, cfgDSes), 
atscfg.ContentTypeCacheURLDotConfig, atscfg.LineCommentCacheURLDotConfig, nil
 }
 
-func GetConfigFileCDNCacheURLPlain(toData *config.TOData) (string, string, 
error) {
+func GetConfigFileCDNCacheURLPlain(toData *config.TOData) (string, string, 
string, error) {
        return GetConfigFileCDNCacheURL(toData, "cacheurl.config")
 }
diff --git a/traffic_ops/ort/atstccfg/cfgfile/cfgfile.go 
b/traffic_ops/ort/atstccfg/cfgfile/cfgfile.go
index ab90c9b..de4813d 100644
--- a/traffic_ops/ort/atstccfg/cfgfile/cfgfile.go
+++ b/traffic_ops/ort/atstccfg/cfgfile/cfgfile.go
@@ -418,8 +418,12 @@ func ParamsToMap(params []tc.Parameter) map[string]string {
        mp := map[string]string{}
        for _, param := range params {
                if val, ok := mp[param.Name]; ok {
-                       log.Errorln("config generation got multiple parameters 
for name '" + param.Name + "' - using '" + val + "'")
-                       continue
+                       if val < param.Value {
+                               log.Errorln("config generation got multiple 
parameters for name '" + param.Name + "' - ignoring '" + param.Value + "'")
+                               continue
+                       } else {
+                               log.Errorln("config generation got multiple 
parameters for name '" + param.Name + "' - ignoring '" + val + "'")
+                       }
                }
                mp[param.Name] = param.Value
        }
diff --git a/traffic_ops/ort/atstccfg/cfgfile/cfgfile_test.go 
b/traffic_ops/ort/atstccfg/cfgfile/cfgfile_test.go
index 0aa1c54..5c41360 100644
--- a/traffic_ops/ort/atstccfg/cfgfile/cfgfile_test.go
+++ b/traffic_ops/ort/atstccfg/cfgfile/cfgfile_test.go
@@ -21,9 +21,13 @@ package cfgfile
 
 import (
        "bytes"
+
+       "math/rand"
        "strings"
        "testing"
+       "time"
 
+       "github.com/apache/trafficcontrol/lib/go-atscfg"
        "github.com/apache/trafficcontrol/lib/go-tc"
        "github.com/apache/trafficcontrol/traffic_ops/ort/atstccfg/config"
 )
@@ -55,13 +59,13 @@ func TestWriteConfigs(t *testing.T) {
 
        actual := buf.String()
 
-       expected0 := "Content-Type: text/plain\r\nPath: 
/my/config0/location/config0.txt\r\n\r\nconfig0\r\n"
+       expected0 := "Content-Type: text/plain\r\nLine-Comment: \r\nPath: 
/my/config0/location/config0.txt\r\n\r\nconfig0\r\n"
 
        if !strings.Contains(actual, expected0) {
-               t.Errorf("WriteConfigs expecte '%v' actual '%v'", expected0, 
actual)
+               t.Errorf("WriteConfigs expected '%v' actual '%v'", expected0, 
actual)
        }
 
-       expected1 := "Content-Type: text/csv\r\nPath: 
/my/config1/location/config1.txt\r\n\r\nconfig2,foo\r\n"
+       expected1 := "Content-Type: text/csv\r\nLine-Comment: \r\nPath: 
/my/config1/location/config1.txt\r\n\r\nconfig2,foo\r\n"
        if !strings.Contains(actual, expected1) {
                t.Errorf("WriteConfigs expected config1 '%v' actual '%v'", 
expected1, actual)
        }
@@ -110,3 +114,462 @@ func TestPreprocessConfigFile(t *testing.T) {
                }
        }
 }
+
+// TestGetAllConfigsWriteConfigsDeterministic tests that 
WriteConfigs(GetAllConfigs()) is Deterministic.
+// That is, that for the same input, it always produces the same output.
+//
+// Because Go map iteration is defined to be random, running it multiple times 
even on the exact same input could be different, if there's a determinism bug.
+// But beyond that, we re-order slices whose order isn't semantically 
significant (e.g. params) and run it again.
+//
+func TestGetAllConfigsWriteConfigsDeterministic(t *testing.T) {
+       // TODO expand fake data. Currently, it's only making a remap.config.
+       toData := MakeFakeTOData()
+       revalOnly := false
+       configs, err := GetAllConfigs(toData, revalOnly)
+       if err != nil {
+               t.Fatalf("error getting configs: " + err.Error())
+       }
+       buf := &bytes.Buffer{}
+       if err := WriteConfigs(configs, buf); err != nil {
+               t.Fatalf("error writing configs: " + err.Error())
+       }
+       configStr := buf.String()
+
+       configStr = removeComments(configStr)
+
+       for i := 0; i < 10; i++ {
+               configs2, err := GetAllConfigs(toData, revalOnly)
+               if err != nil {
+                       t.Fatalf("error getting configs2: " + err.Error())
+               }
+               buf := &bytes.Buffer{}
+               if err := WriteConfigs(configs2, buf); err != nil {
+                       t.Fatalf("error writing configs2: " + err.Error())
+               }
+               configStr2 := buf.String()
+
+               configStr2 = removeComments(configStr2)
+
+               if configStr != configStr2 {
+                       // This doesn't actually need to be fatal; but if there 
are differences, we don't want to spam the error 10 times.
+                       t.Fatalf("multiple configs with the same data expected 
to be deterministically the same, actual '''%v''' and '''%v'''", configStr, 
configStr2)
+               }
+       }
+}
+
+func removeComments(configs string) string {
+       lines := strings.Split(configs, "\n")
+       newLines := []string{}
+       for _, line := range lines {
+               if strings.Contains(line, "DO NOT EDIT") {
+                       continue
+               }
+               newLines = append(newLines, line)
+       }
+       return strings.Join(newLines, "\n")
+}
+
+func randBool() *bool {
+       b := rand.Int()%2 == 0
+       return &b
+}
+
+func randStr() *string {
+       chars := 
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890-_"
+       num := 100
+       s := ""
+       for i := 0; i < num; i++ {
+               s += string(chars[rand.Intn(len(chars))])
+       }
+       return &s
+}
+
+func randInt() *int {
+       i := rand.Int()
+       return &i
+}
+
+func randInt64() *int64 {
+       i := int64(rand.Int63())
+       return &i
+}
+
+func randFloat64() *float64 {
+       f := rand.Float64()
+       return &f
+}
+
+func randDSS() tc.DeliveryServiceServer {
+       return tc.DeliveryServiceServer{
+               Server:          randInt(),
+               DeliveryService: randInt(),
+       }
+}
+
+func randDS() *tc.DeliveryServiceNullable {
+       deepCachingTypeNever := tc.DeepCachingTypeNever
+       dsTypeHTTP := tc.DSTypeHTTP
+       protocol := tc.DSProtocolHTTP
+       return &tc.DeliveryServiceNullable{
+               EcsEnabled:          *randBool(),
+               RangeSliceBlockSize: randInt(),
+               DeliveryServiceNullableV14: tc.DeliveryServiceNullableV14{
+                       ConsistentHashRegex: randStr(),
+                       ConsistentHashQueryParams: []string{
+                               *randStr(),
+                               *randStr(),
+                       },
+                       MaxOriginConnections: randInt(),
+                       DeliveryServiceNullableV13: 
tc.DeliveryServiceNullableV13{
+                               DeepCachingType:   &deepCachingTypeNever,
+                               FQPacingRate:      randInt(),
+                               SigningAlgorithm:  randStr(),
+                               Tenant:            randStr(),
+                               TRResponseHeaders: randStr(),
+                               TRRequestHeaders:  randStr(),
+                               DeliveryServiceNullableV12: 
tc.DeliveryServiceNullableV12{
+                                       DeliveryServiceNullableV11: 
tc.DeliveryServiceNullableV11{
+                                               Active:                   
randBool(),
+                                               AnonymousBlockingEnabled: 
randBool(),
+                                               CacheURL:                 
randStr(),
+                                               CCRDNSTTL:                
randInt(),
+                                               CDNID:                    
randInt(),
+                                               CDNName:                  
randStr(),
+                                               CheckPath:                
randStr(),
+                                               DisplayName:              
randStr(),
+                                               DNSBypassCNAME:           
randStr(),
+                                               DNSBypassIP:              
randStr(),
+                                               DNSBypassIP6:             
randStr(),
+                                               DNSBypassTTL:             
randInt(),
+                                               DSCP:                     
randInt(),
+                                               EdgeHeaderRewrite:        
randStr(),
+                                               GeoLimit:                 
randInt(),
+                                               GeoLimitCountries:        
randStr(),
+                                               GeoLimitRedirectURL:      
randStr(),
+                                               GeoProvider:              
randInt(),
+                                               GlobalMaxMBPS:            
randInt(),
+                                               GlobalMaxTPS:             
randInt(),
+                                               HTTPBypassFQDN:           
randStr(),
+                                               ID:                       
randInt(),
+                                               InfoURL:                  
randStr(),
+                                               InitialDispersion:        
randInt(),
+                                               IPV6RoutingEnabled:       
randBool(),
+                                               LastUpdated:              
&tc.TimeNoMod{Time: time.Now()},
+                                               LogsEnabled:              
randBool(),
+                                               LongDesc:                 
randStr(),
+                                               LongDesc1:                
randStr(),
+                                               LongDesc2:                
randStr(),
+                                               MatchList: 
&[]tc.DeliveryServiceMatch{
+                                                       tc.DeliveryServiceMatch{
+                                                               Type:      
tc.DSMatchTypeHostRegex,
+                                                               SetNumber: 0,
+                                                               Pattern:   
`\.*foo\.*`,
+                                                       },
+                                               },
+                                               MaxDNSAnswers:        randInt(),
+                                               MidHeaderRewrite:     randStr(),
+                                               MissLat:              
randFloat64(),
+                                               MissLong:             
randFloat64(),
+                                               MultiSiteOrigin:      
randBool(),
+                                               OriginShield:         randStr(),
+                                               OrgServerFQDN:        randStr(),
+                                               ProfileDesc:          randStr(),
+                                               ProfileID:            randInt(),
+                                               ProfileName:          randStr(),
+                                               Protocol:             &protocol,
+                                               QStringIgnore:        randInt(),
+                                               RangeRequestHandling: randInt(),
+                                               RegexRemap:           randStr(),
+                                               RegionalGeoBlocking:  
randBool(),
+                                               RemapText:            randStr(),
+                                               RoutingName:          randStr(),
+                                               Signed:               
*randBool(),
+                                               SSLKeyVersion:        randInt(),
+                                               TenantID:             randInt(),
+                                               Type:                 
&dsTypeHTTP,
+                                               TypeID:               randInt(),
+                                               XMLID:                randStr(),
+                                               ExampleURLs: []string{
+                                                       *randStr(),
+                                                       *randStr(),
+                                               },
+                                       },
+                               },
+                       },
+               },
+       }
+}
+
+func randServer() *tc.Server {
+       return &tc.Server{
+               Cachegroup:     *randStr(),
+               CachegroupID:   *randInt(),
+               CDNID:          *randInt(),
+               CDNName:        *randStr(),
+               DomainName:     *randStr(),
+               FQDN:           &*randStr(),
+               FqdnTime:       time.Now(),
+               GUID:           *randStr(),
+               HostName:       *randStr(),
+               HTTPSPort:      *randInt(),
+               ID:             *randInt(),
+               ILOIPAddress:   *randStr(),
+               ILOIPGateway:   *randStr(),
+               ILOIPNetmask:   *randStr(),
+               ILOPassword:    *randStr(),
+               ILOUsername:    *randStr(),
+               InterfaceMtu:   *randInt(),
+               InterfaceName:  *randStr(),
+               IP6Address:     *randStr(),
+               IP6IsService:   *randBool(),
+               IP6Gateway:     *randStr(),
+               IPAddress:      *randStr(),
+               IPIsService:    *randBool(),
+               IPGateway:      *randStr(),
+               IPNetmask:      *randStr(),
+               LastUpdated:    tc.TimeNoMod{Time: time.Now()},
+               MgmtIPAddress:  *randStr(),
+               MgmtIPGateway:  *randStr(),
+               MgmtIPNetmask:  *randStr(),
+               OfflineReason:  *randStr(),
+               PhysLocation:   *randStr(),
+               PhysLocationID: *randInt(),
+               Profile:        *randStr(),
+               ProfileDesc:    *randStr(),
+               ProfileID:      *randInt(),
+               Rack:           *randStr(),
+               RevalPending:   *randBool(),
+               RouterHostName: *randStr(),
+               RouterPortName: *randStr(),
+               Status:         *randStr(),
+               StatusID:       *randInt(),
+               TCPPort:        *randInt(),
+               Type:           *randStr(),
+               TypeID:         *randInt(),
+               UpdPending:     *randBool(),
+               XMPPID:         *randStr(),
+               XMPPPasswd:     *randStr(),
+       }
+}
+
+func randCacheGroup() *tc.CacheGroupNullable {
+       return &tc.CacheGroupNullable{
+               ID:        randInt(),
+               Name:      randStr(),
+               ShortName: randStr(),
+               Latitude:  randFloat64(),
+               Longitude: randFloat64(),
+               // ParentName:                  randStr(),
+               // ParentCachegroupID:          randInt(),
+               // SecondaryParentName:         randStr(),
+               // SecondaryParentCachegroupID: randInt(),
+               FallbackToClosest: randBool(),
+               Type:              randStr(),
+               TypeID:            randInt(),
+               LastUpdated:       &tc.TimeNoMod{Time: time.Now()},
+               Fallbacks: &[]string{
+                       *randStr(),
+                       *randStr(),
+               },
+       }
+}
+
+func randParam() *tc.Parameter {
+       return &tc.Parameter{
+               ConfigFile: *randStr(),
+               Name:       *randStr(),
+               Value:      *randStr(),
+               Profiles:   []byte(`[]`),
+       }
+}
+
+func randJob() *tc.Job {
+       return &tc.Job{
+               Parameters:      *randStr(),
+               Keyword:         *randStr(),
+               AssetURL:        *randStr(),
+               CreatedBy:       *randStr(),
+               StartTime:       *randStr(),
+               ID:              *randInt64(),
+               DeliveryService: *randStr(),
+       }
+}
+
+func randCDN() *tc.CDN {
+       return &tc.CDN{
+               DNSSECEnabled: *randBool(),
+               DomainName:    *randStr(),
+               Name:          *randStr(),
+       }
+}
+
+func randDSRs() *tc.DeliveryServiceRegexes {
+       return &tc.DeliveryServiceRegexes{
+               Regexes: []tc.DeliveryServiceRegex{
+                       *randDSR(),
+                       *randDSR(),
+               },
+               DSName: *randStr(),
+       }
+}
+
+func randDSR() *tc.DeliveryServiceRegex {
+       return &tc.DeliveryServiceRegex{
+               Type:      string(tc.DSMatchTypeHostRegex),
+               SetNumber: *randInt(),
+               Pattern:   `\.*foo\.*`,
+       }
+}
+
+func randCDNSSLKeys() *tc.CDNSSLKeys {
+       return &tc.CDNSSLKeys{
+               DeliveryService: *randStr(),
+               Certificate: tc.CDNSSLKeysCertificate{
+                       Crt: *randStr(),
+                       Key: *randStr(),
+               },
+               Hostname: *randStr(),
+       }
+}
+
+func MakeFakeTOData() *config.TOData {
+       cg0 := *randCacheGroup()
+       cg0.ParentName = nil
+       cg0.ParentCachegroupID = nil
+
+       cg1 := *randCacheGroup()
+       cg1.ParentName = cg0.Name
+       cg1.ParentCachegroupID = cg0.ID
+
+       sv0 := *randServer()
+       sv1 := *randServer()
+       sv2 := *randServer()
+
+       sv0.Cachegroup = *cg0.Name
+       sv1.Cachegroup = *cg0.Name
+       sv2.Cachegroup = *cg1.Name
+
+       ds0 := *randDS()
+       ds1 := *randDS()
+
+       dss := []tc.DeliveryServiceServer{
+               tc.DeliveryServiceServer{
+                       Server:          &sv0.ID,
+                       DeliveryService: ds0.ID,
+               },
+               tc.DeliveryServiceServer{
+                       Server:          &sv0.ID,
+                       DeliveryService: ds1.ID,
+               },
+               tc.DeliveryServiceServer{
+                       Server:          &sv1.ID,
+                       DeliveryService: ds0.ID,
+               },
+       }
+
+       dsr0 := randDSRs()
+       dsr0.DSName = *ds0.XMLID
+       dsr0.Regexes[0].Pattern = `\.*foo\.*`
+       // ds1.Pattern = `\.*bar\.*`
+
+       dsr1 := randDSRs()
+       dsr1.DSName = *ds1.XMLID
+
+       return &config.TOData{
+               CacheGroups: []tc.CacheGroupNullable{
+                       cg0,
+                       cg1,
+               },
+               GlobalParams: []tc.Parameter{
+                       *randParam(),
+                       *randParam(),
+                       *randParam(),
+               },
+               ScopeParams: []tc.Parameter{
+                       *randParam(),
+                       *randParam(),
+                       *randParam(),
+               },
+               ServerParams: []tc.Parameter{
+                       // configLocation := 
locationParams["remap.config"].Location
+                       tc.Parameter{
+                               ConfigFile: "remap.config",
+                               Name:       "location",
+                               Value:      "/etc/trafficserver",
+                               Profiles:   []byte(`[]`),
+                       },
+                       *randParam(),
+                       *randParam(),
+                       *randParam(),
+               },
+               CacheKeyParams: []tc.Parameter{
+                       *randParam(),
+                       *randParam(),
+                       *randParam(),
+               },
+               ParentConfigParams: []tc.Parameter{
+                       *randParam(),
+                       *randParam(),
+                       *randParam(),
+               },
+               DeliveryServices: []tc.DeliveryServiceNullable{
+                       ds0,
+                       ds1,
+               },
+               Servers: []tc.Server{
+                       sv1,
+                       sv2,
+               },
+               DeliveryServiceServers: dss,
+               Server:                 sv0,
+               TOToolName:             *randStr(),
+               TOURL:                  *randStr(),
+               Jobs: []tc.Job{
+                       *randJob(),
+                       *randJob(),
+               },
+               CDN: *randCDN(),
+               DeliveryServiceRegexes: []tc.DeliveryServiceRegexes{
+                       *dsr0,
+                       *dsr1,
+               },
+               URISigningKeys: map[tc.DeliveryServiceName][]byte{
+                       tc.DeliveryServiceName(*randStr()): []byte(*randStr()),
+                       tc.DeliveryServiceName(*randStr()): []byte(*randStr()),
+               },
+               URLSigKeys: map[tc.DeliveryServiceName]tc.URLSigKeys{
+                       tc.DeliveryServiceName(*randStr()): map[string]string{
+                               *randStr(): *randStr(),
+                               *randStr(): *randStr(),
+                       },
+                       tc.DeliveryServiceName(*randStr()): map[string]string{
+                               *randStr(): *randStr(),
+                               *randStr(): *randStr(),
+                       },
+               },
+               ServerCapabilities: 
map[int]map[atscfg.ServerCapability]struct{}{
+                       *randInt(): map[atscfg.ServerCapability]struct{}{
+                               atscfg.ServerCapability(*randStr()): struct{}{},
+                               atscfg.ServerCapability(*randStr()): struct{}{},
+                       },
+                       *randInt(): map[atscfg.ServerCapability]struct{}{
+                               atscfg.ServerCapability(*randStr()): struct{}{},
+                               atscfg.ServerCapability(*randStr()): struct{}{},
+                       },
+               },
+               DSRequiredCapabilities: 
map[int]map[atscfg.ServerCapability]struct{}{
+                       *randInt(): map[atscfg.ServerCapability]struct{}{
+                               atscfg.ServerCapability(*randStr()): struct{}{},
+                               atscfg.ServerCapability(*randStr()): struct{}{},
+                       },
+                       *randInt(): map[atscfg.ServerCapability]struct{}{
+                               atscfg.ServerCapability(*randStr()): struct{}{},
+                               atscfg.ServerCapability(*randStr()): struct{}{},
+                       },
+               },
+               SSLKeys: []tc.CDNSSLKeys{
+                       *randCDNSSLKeys(),
+                       *randCDNSSLKeys(),
+               },
+       }
+}
diff --git a/traffic_ops/ort/atstccfg/cfgfile/chkconfig.go 
b/traffic_ops/ort/atstccfg/cfgfile/chkconfig.go
index ac56f0c..c96872d 100644
--- a/traffic_ops/ort/atstccfg/cfgfile/chkconfig.go
+++ b/traffic_ops/ort/atstccfg/cfgfile/chkconfig.go
@@ -24,7 +24,7 @@ import (
        "github.com/apache/trafficcontrol/traffic_ops/ort/atstccfg/config"
 )
 
-func GetConfigFileServerChkconfig(toData *config.TOData) (string, string, 
error) {
+func GetConfigFileServerChkconfig(toData *config.TOData) (string, string, 
string, error) {
        fileParams := map[string][]string{}
        for _, param := range toData.ServerParams {
                if param.ConfigFile != atscfg.ChkconfigParamConfigFile {
@@ -33,5 +33,5 @@ func GetConfigFileServerChkconfig(toData *config.TOData) 
(string, string, error)
                fileParams[param.Name] = append(fileParams[param.Name], 
param.Value)
        }
 
-       return atscfg.MakeChkconfig(fileParams), atscfg.ContentTypeChkconfig, 
nil
+       return atscfg.MakeChkconfig(fileParams), atscfg.ContentTypeChkconfig, 
atscfg.LineCommentChkconfig, nil
 }
diff --git a/traffic_ops/ort/atstccfg/cfgfile/dropqstringdotconfig.go 
b/traffic_ops/ort/atstccfg/cfgfile/dropqstringdotconfig.go
index 9cc7262..4474ea8 100644
--- a/traffic_ops/ort/atstccfg/cfgfile/dropqstringdotconfig.go
+++ b/traffic_ops/ort/atstccfg/cfgfile/dropqstringdotconfig.go
@@ -24,7 +24,7 @@ import (
        "github.com/apache/trafficcontrol/traffic_ops/ort/atstccfg/config"
 )
 
-func GetConfigFileProfileDropQStringDotConfig(toData *config.TOData) (string, 
string, error) {
+func GetConfigFileProfileDropQStringDotConfig(toData *config.TOData) (string, 
string, string, error) {
        dropQStringVal := (*string)(nil)
        for _, param := range toData.ServerParams {
                if param.ConfigFile != atscfg.DropQStringDotConfigFileName {
@@ -37,5 +37,5 @@ func GetConfigFileProfileDropQStringDotConfig(toData 
*config.TOData) (string, st
                break
        }
 
-       return atscfg.MakeDropQStringDotConfig(toData.Server.Profile, 
toData.TOToolName, toData.TOURL, dropQStringVal), 
atscfg.ContentTypeDropQStringDotConfig, nil
+       return atscfg.MakeDropQStringDotConfig(toData.Server.Profile, 
toData.TOToolName, toData.TOURL, dropQStringVal), 
atscfg.ContentTypeDropQStringDotConfig, atscfg.LineCommentDropQStringDotConfig, 
nil
 }
diff --git a/traffic_ops/ort/atstccfg/cfgfile/facts.go 
b/traffic_ops/ort/atstccfg/cfgfile/facts.go
index 43c1a63..6cace4f 100644
--- a/traffic_ops/ort/atstccfg/cfgfile/facts.go
+++ b/traffic_ops/ort/atstccfg/cfgfile/facts.go
@@ -24,6 +24,6 @@ import (
        "github.com/apache/trafficcontrol/traffic_ops/ort/atstccfg/config"
 )
 
-func GetConfigFileProfile12MFacts(toData *config.TOData) (string, string, 
error) {
-       return atscfg.Make12MFacts(toData.Server.Profile, toData.TOToolName, 
toData.TOURL), atscfg.ContentType12MFacts, nil
+func GetConfigFileProfile12MFacts(toData *config.TOData) (string, string, 
string, error) {
+       return atscfg.Make12MFacts(toData.Server.Profile, toData.TOToolName, 
toData.TOURL), atscfg.ContentType12MFacts, atscfg.LineComment12MFacts, nil
 }
diff --git a/traffic_ops/ort/atstccfg/cfgfile/headerrewritedotconfig.go 
b/traffic_ops/ort/atstccfg/cfgfile/headerrewritedotconfig.go
index ee9446e..be13ba0 100644
--- a/traffic_ops/ort/atstccfg/cfgfile/headerrewritedotconfig.go
+++ b/traffic_ops/ort/atstccfg/cfgfile/headerrewritedotconfig.go
@@ -28,7 +28,7 @@ import (
        "github.com/apache/trafficcontrol/traffic_ops/ort/atstccfg/config"
 )
 
-func GetConfigFileCDNHeaderRewrite(toData *config.TOData, fileName string) 
(string, string, error) {
+func GetConfigFileCDNHeaderRewrite(toData *config.TOData, fileName string) 
(string, string, string, error) {
        dsName := strings.TrimSuffix(strings.TrimPrefix(fileName, 
atscfg.HeaderRewritePrefix), atscfg.ConfigSuffix) // TODO verify prefix and 
suffix? Perl doesn't
 
        tcDS := tc.DeliveryServiceNullable{}
@@ -40,16 +40,16 @@ func GetConfigFileCDNHeaderRewrite(toData *config.TOData, 
fileName string) (stri
                break
        }
        if tcDS.ID == nil {
-               return "", "", errors.New("ds '" + dsName + "' not found")
+               return "", "", "", errors.New("ds '" + dsName + "' not found")
        }
 
        if tcDS.CDNName == nil {
-               return "", "", errors.New("ds '" + dsName + "' missing cdn")
+               return "", "", "", errors.New("ds '" + dsName + "' missing cdn")
        }
 
        cfgDS, err := atscfg.HeaderRewriteDSFromDS(&tcDS)
        if err != nil {
-               return "", "", errors.New("converting ds to config ds: " + 
err.Error())
+               return "", "", "", errors.New("converting ds to config ds: " + 
err.Error())
        }
 
        dsServers := FilterDSS(toData.DeliveryServiceServers, 
map[int]struct{}{cfgDS.ID: {}}, nil)
@@ -80,5 +80,5 @@ func GetConfigFileCDNHeaderRewrite(toData *config.TOData, 
fileName string) (stri
                assignedEdges = append(assignedEdges, cfgServer)
        }
 
-       return 
atscfg.MakeHeaderRewriteDotConfig(tc.CDNName(toData.Server.CDNName), 
toData.TOToolName, toData.TOURL, cfgDS, assignedEdges), 
atscfg.ContentTypeHeaderRewriteDotConfig, nil
+       return 
atscfg.MakeHeaderRewriteDotConfig(tc.CDNName(toData.Server.CDNName), 
toData.TOToolName, toData.TOURL, cfgDS, assignedEdges), 
atscfg.ContentTypeHeaderRewriteDotConfig, 
atscfg.LineCommentHeaderRewriteDotConfig, nil
 }
diff --git a/traffic_ops/ort/atstccfg/cfgfile/headerrewritemiddotconfig.go 
b/traffic_ops/ort/atstccfg/cfgfile/headerrewritemiddotconfig.go
index 766f638..460e3b9 100644
--- a/traffic_ops/ort/atstccfg/cfgfile/headerrewritemiddotconfig.go
+++ b/traffic_ops/ort/atstccfg/cfgfile/headerrewritemiddotconfig.go
@@ -28,7 +28,7 @@ import (
        "github.com/apache/trafficcontrol/traffic_ops/ort/atstccfg/config"
 )
 
-func GetConfigFileCDNHeaderRewriteMid(toData *config.TOData, fileName string) 
(string, string, error) {
+func GetConfigFileCDNHeaderRewriteMid(toData *config.TOData, fileName string) 
(string, string, string, error) {
        dsName := strings.TrimSuffix(strings.TrimPrefix(fileName, 
atscfg.HeaderRewriteMidPrefix), atscfg.ConfigSuffix) // TODO verify prefix and 
suffix? Perl doesn't
 
        tcDS := tc.DeliveryServiceNullable{}
@@ -40,16 +40,16 @@ func GetConfigFileCDNHeaderRewriteMid(toData 
*config.TOData, fileName string) (s
                break
        }
        if tcDS.ID == nil {
-               return "", "", errors.New("ds '" + dsName + "' not found")
+               return "", "", "", errors.New("ds '" + dsName + "' not found")
        }
 
        if tcDS.CDNName == nil {
-               return "", "", errors.New("ds '" + dsName + "' missing cdn")
+               return "", "", "", errors.New("ds '" + dsName + "' missing cdn")
        }
 
        cfgDS, err := atscfg.HeaderRewriteDSFromDS(&tcDS)
        if err != nil {
-               return "", "", errors.New("converting ds to config ds: " + 
err.Error())
+               return "", "", "", errors.New("converting ds to config ds: " + 
err.Error())
        }
 
        dsServers := FilterDSS(toData.DeliveryServiceServers, 
map[int]struct{}{cfgDS.ID: {}}, nil)
@@ -113,5 +113,5 @@ func GetConfigFileCDNHeaderRewriteMid(toData 
*config.TOData, fileName string) (s
                assignedMids = append(assignedMids, cfgServer)
        }
 
-       return 
atscfg.MakeHeaderRewriteMidDotConfig(tc.CDNName(toData.Server.CDNName), 
toData.TOToolName, toData.TOURL, cfgDS, assignedMids), 
atscfg.ContentTypeHeaderRewriteDotConfig, nil
+       return 
atscfg.MakeHeaderRewriteMidDotConfig(tc.CDNName(toData.Server.CDNName), 
toData.TOToolName, toData.TOURL, cfgDS, assignedMids), 
atscfg.ContentTypeHeaderRewriteDotConfig, 
atscfg.LineCommentHeaderRewriteDotConfig, nil
 }
diff --git a/traffic_ops/ort/atstccfg/cfgfile/hostingdotconfig.go 
b/traffic_ops/ort/atstccfg/cfgfile/hostingdotconfig.go
index fc5f301..6c4ac36 100644
--- a/traffic_ops/ort/atstccfg/cfgfile/hostingdotconfig.go
+++ b/traffic_ops/ort/atstccfg/cfgfile/hostingdotconfig.go
@@ -31,7 +31,7 @@ import (
 const ServerHostingDotConfigMidIncludeInactive = false
 const ServerHostingDotConfigEdgeIncludeInactive = true
 
-func GetConfigFileServerHostingDotConfig(toData *config.TOData) (string, 
string, error) {
+func GetConfigFileServerHostingDotConfig(toData *config.TOData) (string, 
string, string, error) {
        fileParams := ParamsToMap(FilterParams(toData.ServerParams, 
atscfg.HostingConfigParamConfigFile, "", "", ""))
 
        cdnServers := map[tc.CacheName]tc.Server{}
@@ -59,7 +59,7 @@ func GetConfigFileServerHostingDotConfig(toData 
*config.TOData) (string, string,
        dsServerMap := map[int]map[int]struct{}{} // set[dsID][serverID]
        for _, dss := range dsServers {
                if dss.Server == nil || dss.DeliveryService == nil {
-                       return "", "", errors.New("deliveryserviceservers 
returned dss with nil values")
+                       return "", "", "", errors.New("deliveryserviceservers 
returned dss with nil values")
                }
                if _, ok := dsServerMap[*dss.DeliveryService]; !ok {
                        dsServerMap[*dss.DeliveryService] = map[int]struct{}{}
@@ -121,5 +121,5 @@ func GetConfigFileServerHostingDotConfig(toData 
*config.TOData) (string, string,
                origins = append(origins, origin)
        }
 
-       return 
atscfg.MakeHostingDotConfig(tc.CacheName(toData.Server.HostName), 
toData.TOToolName, toData.TOURL, fileParams, origins), 
atscfg.ContentTypeHostingDotConfig, nil
+       return 
atscfg.MakeHostingDotConfig(tc.CacheName(toData.Server.HostName), 
toData.TOToolName, toData.TOURL, fileParams, origins), 
atscfg.ContentTypeHostingDotConfig, atscfg.LineCommentHostingDotConfig, nil
 }
diff --git a/traffic_ops/ort/atstccfg/cfgfile/ipallowdotconfig.go 
b/traffic_ops/ort/atstccfg/cfgfile/ipallowdotconfig.go
index 698abe9..8c7489c 100644
--- a/traffic_ops/ort/atstccfg/cfgfile/ipallowdotconfig.go
+++ b/traffic_ops/ort/atstccfg/cfgfile/ipallowdotconfig.go
@@ -28,20 +28,20 @@ import (
        "github.com/apache/trafficcontrol/traffic_ops/ort/atstccfg/config"
 )
 
-func GetConfigFileServerIPAllowDotConfig(toData *config.TOData) (string, 
string, error) {
+func GetConfigFileServerIPAllowDotConfig(toData *config.TOData) (string, 
string, string, error) {
        fileParams := ParamsToMultiMap(FilterParams(toData.ServerParams, 
atscfg.IPAllowConfigFileName, "", "", ""))
 
        cgMap := map[string]tc.CacheGroupNullable{}
        for _, cg := range toData.CacheGroups {
                if cg.Name == nil {
-                       return "", "", errors.New("got cachegroup with nil 
name!'")
+                       return "", "", "", errors.New("got cachegroup with nil 
name!'")
                }
                cgMap[*cg.Name] = cg
        }
 
        serverCG, ok := cgMap[toData.Server.Cachegroup]
        if !ok {
-               return "", "", errors.New("server cachegroup not in 
cachegroups!")
+               return "", "", "", errors.New("server cachegroup not in 
cachegroups!")
        }
 
        childCGs := map[string]tc.CacheGroupNullable{}
@@ -59,5 +59,5 @@ func GetConfigFileServerIPAllowDotConfig(toData 
*config.TOData) (string, string,
                }
        }
 
-       return 
atscfg.MakeIPAllowDotConfig(tc.CacheName(toData.Server.HostName), 
tc.CacheType(toData.Server.Type), toData.TOToolName, toData.TOURL, fileParams, 
childServers), atscfg.ContentTypeIPAllowDotConfig, nil
+       return 
atscfg.MakeIPAllowDotConfig(tc.CacheName(toData.Server.HostName), 
tc.CacheType(toData.Server.Type), toData.TOToolName, toData.TOURL, fileParams, 
childServers), atscfg.ContentTypeIPAllowDotConfig, 
atscfg.LineCommentIPAllowDotConfig, nil
 }
diff --git a/traffic_ops/ort/atstccfg/cfgfile/loggingdotconfig.go 
b/traffic_ops/ort/atstccfg/cfgfile/loggingdotconfig.go
index 7593ab4..99b0648 100644
--- a/traffic_ops/ort/atstccfg/cfgfile/loggingdotconfig.go
+++ b/traffic_ops/ort/atstccfg/cfgfile/loggingdotconfig.go
@@ -24,7 +24,7 @@ import (
        "github.com/apache/trafficcontrol/traffic_ops/ort/atstccfg/config"
 )
 
-func GetConfigFileProfileLoggingDotConfig(toData *config.TOData) (string, 
string, error) {
+func GetConfigFileProfileLoggingDotConfig(toData *config.TOData) (string, 
string, string, error) {
        params := ParamsToMap(FilterParams(toData.ServerParams, 
atscfg.LoggingFileName, "", "", "location"))
-       return atscfg.MakeLoggingDotConfig(toData.Server.Profile, params, 
toData.TOToolName, toData.TOURL), atscfg.ContentTypeLoggingDotConfig, nil
+       return atscfg.MakeLoggingDotConfig(toData.Server.Profile, params, 
toData.TOToolName, toData.TOURL), atscfg.ContentTypeLoggingDotConfig, 
atscfg.LineCommentLoggingDotConfig, nil
 }
diff --git a/traffic_ops/ort/atstccfg/cfgfile/loggingdotyaml.go 
b/traffic_ops/ort/atstccfg/cfgfile/loggingdotyaml.go
index 642a631..22cfc21 100644
--- a/traffic_ops/ort/atstccfg/cfgfile/loggingdotyaml.go
+++ b/traffic_ops/ort/atstccfg/cfgfile/loggingdotyaml.go
@@ -24,7 +24,7 @@ import (
        "github.com/apache/trafficcontrol/traffic_ops/ort/atstccfg/config"
 )
 
-func GetConfigFileProfileLoggingDotYAML(toData *config.TOData) (string, 
string, error) {
+func GetConfigFileProfileLoggingDotYAML(toData *config.TOData) (string, 
string, string, error) {
        params := ParamsToMap(FilterParams(toData.ServerParams, 
atscfg.LoggingYAMLFileName, "", "", "location"))
-       return atscfg.MakeLoggingDotYAML(toData.Server.Profile, params, 
toData.TOToolName, toData.TOURL), atscfg.ContentTypeLoggingDotYAML, nil
+       return atscfg.MakeLoggingDotYAML(toData.Server.Profile, params, 
toData.TOToolName, toData.TOURL), atscfg.ContentTypeLoggingDotYAML, 
atscfg.LineCommentLoggingDotYAML, nil
 }
diff --git a/traffic_ops/ort/atstccfg/cfgfile/logsxmldotconfig.go 
b/traffic_ops/ort/atstccfg/cfgfile/logsxmldotconfig.go
index becfa40..32265d9 100644
--- a/traffic_ops/ort/atstccfg/cfgfile/logsxmldotconfig.go
+++ b/traffic_ops/ort/atstccfg/cfgfile/logsxmldotconfig.go
@@ -24,7 +24,7 @@ import (
        "github.com/apache/trafficcontrol/traffic_ops/ort/atstccfg/config"
 )
 
-func GetConfigFileProfileLogsXMLDotConfig(toData *config.TOData) (string, 
string, error) {
+func GetConfigFileProfileLogsXMLDotConfig(toData *config.TOData) (string, 
string, string, error) {
        params := ParamsToMap(FilterParams(toData.ServerParams, 
atscfg.LogsXMLFileName, "", "", "location"))
-       return atscfg.MakeLogsXMLDotConfig(toData.Server.Profile, params, 
toData.TOToolName, toData.TOURL), atscfg.ContentTypeLogsDotXML, nil
+       return atscfg.MakeLogsXMLDotConfig(toData.Server.Profile, params, 
toData.TOToolName, toData.TOURL), atscfg.ContentTypeLogsDotXML, 
atscfg.LineCommentLogsDotXML, nil
 }
diff --git a/traffic_ops/ort/atstccfg/cfgfile/meta.go 
b/traffic_ops/ort/atstccfg/cfgfile/meta.go
index d4dfd00..655e513 100644
--- a/traffic_ops/ort/atstccfg/cfgfile/meta.go
+++ b/traffic_ops/ort/atstccfg/cfgfile/meta.go
@@ -109,10 +109,7 @@ func GetMeta(toData *config.TOData) 
(*tc.ATSConfigMetaData, error) {
                }
        }
 
-       scopeParams := map[string]string{}
-       for _, param := range toData.ScopeParams {
-               scopeParams[param.ConfigFile] = param.Value
-       }
+       scopeParams := ParamsToMap(toData.ScopeParams)
 
        locationParams := map[string]atscfg.ConfigProfileParams{}
        for _, param := range toData.ServerParams {
diff --git a/traffic_ops/ort/atstccfg/cfgfile/packages.go 
b/traffic_ops/ort/atstccfg/cfgfile/packages.go
index 8f1857c..97e1120 100644
--- a/traffic_ops/ort/atstccfg/cfgfile/packages.go
+++ b/traffic_ops/ort/atstccfg/cfgfile/packages.go
@@ -24,7 +24,7 @@ import (
        "github.com/apache/trafficcontrol/traffic_ops/ort/atstccfg/config"
 )
 
-func GetConfigFileServerPackages(toData *config.TOData) (string, string, 
error) {
+func GetConfigFileServerPackages(toData *config.TOData) (string, string, 
string, error) {
        params := ParamsToMultiMap(FilterParams(toData.ServerParams, 
atscfg.PackagesParamConfigFile, "", "", ""))
-       return atscfg.MakePackages(params), atscfg.ContentTypePackages, nil
+       return atscfg.MakePackages(params), atscfg.ContentTypePackages, 
atscfg.LineCommentPackages, nil
 }
diff --git a/traffic_ops/ort/atstccfg/cfgfile/parentdotconfig.go 
b/traffic_ops/ort/atstccfg/cfgfile/parentdotconfig.go
index 1b4a6ba..75b6eef 100644
--- a/traffic_ops/ort/atstccfg/cfgfile/parentdotconfig.go
+++ b/traffic_ops/ort/atstccfg/cfgfile/parentdotconfig.go
@@ -33,18 +33,18 @@ import (
        "github.com/apache/trafficcontrol/traffic_ops/ort/atstccfg/config"
 )
 
-func GetConfigFileServerParentDotConfig(toData *config.TOData) (string, 
string, error) {
+func GetConfigFileServerParentDotConfig(toData *config.TOData) (string, 
string, string, error) {
        cgMap := map[string]tc.CacheGroupNullable{}
        for _, cg := range toData.CacheGroups {
                if cg.Name == nil {
-                       return "", "", errors.New("got cachegroup with nil 
name!'")
+                       return "", "", "", errors.New("got cachegroup with nil 
name!'")
                }
                cgMap[*cg.Name] = cg
        }
 
        serverCG, ok := cgMap[toData.Server.Cachegroup]
        if !ok {
-               return "", "", errors.New("server '" + toData.Server.HostName + 
"' cachegroup '" + toData.Server.Cachegroup + "' not found in CacheGroups")
+               return "", "", "", errors.New("server '" + 
toData.Server.HostName + "' cachegroup '" + toData.Server.Cachegroup + "' not 
found in CacheGroups")
        }
 
        parentCGID := -1
@@ -52,15 +52,15 @@ func GetConfigFileServerParentDotConfig(toData 
*config.TOData) (string, string,
        if serverCG.ParentName != nil && *serverCG.ParentName != "" {
                parentCG, ok := cgMap[*serverCG.ParentName]
                if !ok {
-                       return "", "", errors.New("server '" + 
toData.Server.HostName + "' cachegroup '" + toData.Server.Cachegroup + "' 
parent '" + *serverCG.ParentName + "' not found in CacheGroups")
+                       return "", "", "", errors.New("server '" + 
toData.Server.HostName + "' cachegroup '" + toData.Server.Cachegroup + "' 
parent '" + *serverCG.ParentName + "' not found in CacheGroups")
                }
                if parentCG.ID == nil {
-                       return "", "", errors.New("got cachegroup '" + 
*parentCG.Name + "' with nil ID!'")
+                       return "", "", "", errors.New("got cachegroup '" + 
*parentCG.Name + "' with nil ID!'")
                }
                parentCGID = *parentCG.ID
 
                if parentCG.Type == nil {
-                       return "", "", errors.New("got cachegroup '" + 
*parentCG.Name + "' with nil Type!'")
+                       return "", "", "", errors.New("got cachegroup '" + 
*parentCG.Name + "' with nil Type!'")
                }
                parentCGType = *parentCG.Type
        }
@@ -70,15 +70,15 @@ func GetConfigFileServerParentDotConfig(toData 
*config.TOData) (string, string,
        if serverCG.SecondaryParentName != nil && *serverCG.SecondaryParentName 
!= "" {
                parentCG, ok := cgMap[*serverCG.SecondaryParentName]
                if !ok {
-                       return "", "", errors.New("server '" + 
toData.Server.HostName + "' cachegroup '" + toData.Server.Cachegroup + "' 
secondary parent '" + *serverCG.SecondaryParentName + "' not found in 
CacheGroups")
+                       return "", "", "", errors.New("server '" + 
toData.Server.HostName + "' cachegroup '" + toData.Server.Cachegroup + "' 
secondary parent '" + *serverCG.SecondaryParentName + "' not found in 
CacheGroups")
                }
 
                if parentCG.ID == nil {
-                       return "", "", errors.New("got cachegroup '" + 
*parentCG.Name + "' with nil ID!'")
+                       return "", "", "", errors.New("got cachegroup '" + 
*parentCG.Name + "' with nil ID!'")
                }
                secondaryParentCGID = *parentCG.ID
                if parentCG.Type == nil {
-                       return "", "", errors.New("got cachegroup '" + 
*parentCG.Name + "' with nil Type!'")
+                       return "", "", "", errors.New("got cachegroup '" + 
*parentCG.Name + "' with nil Type!'")
                }
 
                secondaryParentCGType = *parentCG.Type
@@ -107,10 +107,10 @@ func GetConfigFileServerParentDotConfig(toData 
*config.TOData) (string, string,
                log.Infoln("This cache Is Top Level!")
                for _, cg := range toData.CacheGroups {
                        if cg.Type == nil {
-                               return "", "", errors.New("cachegroup type is 
nil!")
+                               return "", "", "", errors.New("cachegroup type 
is nil!")
                        }
                        if cg.Name == nil {
-                               return "", "", errors.New("cachegroup type is 
nil!")
+                               return "", "", "", errors.New("cachegroup type 
is nil!")
                        }
 
                        if *cg.Type != tc.CacheGroupOriginTypeName {
@@ -120,14 +120,14 @@ func GetConfigFileServerParentDotConfig(toData 
*config.TOData) (string, string,
                }
        } else {
                if toData.Server.Cachegroup == "" {
-                       return "", "", errors.New("server cachegroup is nil!")
+                       return "", "", "", errors.New("server cachegroup is 
nil!")
                }
                for _, cg := range toData.CacheGroups {
                        if cg.Type == nil {
-                               return "", "", errors.New("cachegroup type is 
nil!")
+                               return "", "", "", errors.New("cachegroup type 
is nil!")
                        }
                        if cg.Name == nil {
-                               return "", "", errors.New("cachegroup type is 
nil!")
+                               return "", "", "", errors.New("cachegroup type 
is nil!")
                        }
 
                        if *cg.Name == toData.Server.Cachegroup {
@@ -172,7 +172,7 @@ func GetConfigFileServerParentDotConfig(toData 
*config.TOData) (string, string,
        parentServerDSes := map[int]map[int]struct{}{} // map[serverID][dsID] 
// cgServerDSes
        for _, dss := range cgDSServers {
                if dss.Server == nil || dss.DeliveryService == nil {
-                       return "", "", errors.New("getting parent.config 
cachegroup parent server delivery service servers: got dss with nil members!")
+                       return "", "", "", errors.New("getting parent.config 
cachegroup parent server delivery service servers: got dss with nil members!")
                }
                if parentServerDSes[*dss.Server] == nil {
                        parentServerDSes[*dss.Server] = map[int]struct{}{}
@@ -194,12 +194,12 @@ func GetConfigFileServerParentDotConfig(toData 
*config.TOData) (string, string,
 
        atsMajorVer, err := 
atscfg.GetATSMajorVersionFromATSVersion(atsVersionParam)
        if err != nil {
-               return "", "", errors.New("getting ATS major version from 
version parameter (profile '" + toData.Server.Profile + "' configFile 'package' 
name 'trafficserver'): " + err.Error())
+               return "", "", "", errors.New("getting ATS major version from 
version parameter (profile '" + toData.Server.Profile + "' configFile 'package' 
name 'trafficserver'): " + err.Error())
        }
 
        parentConfigParamsWithProfiles, err := 
TCParamsToParamsWithProfiles(toData.ParentConfigParams)
        if err != nil {
-               return "", "", errors.New("unmarshalling parent.config 
parameters profiles: " + err.Error())
+               return "", "", "", errors.New("unmarshalling parent.config 
parameters profiles: " + err.Error())
        }
 
        // this is an optimization, to avoid looping over all params, for every 
DS. Instead, we loop over all params only once, and put them in a profile map.
@@ -455,7 +455,7 @@ func GetConfigFileServerParentDotConfig(toData 
*config.TOData) (string, string,
 
        parentInfos := atscfg.MakeParentInfo(&serverInfo, serverCDNDomain, 
profileCaches, originServers)
 
-       return atscfg.MakeParentDotConfig(&serverInfo, atsMajorVer, 
toData.TOToolName, toData.TOURL, parentConfigDSes, serverParams, parentInfos), 
atscfg.ContentTypeParentDotConfig, nil
+       return atscfg.MakeParentDotConfig(&serverInfo, atsMajorVer, 
toData.TOToolName, toData.TOURL, parentConfigDSes, serverParams, parentInfos), 
atscfg.ContentTypeParentDotConfig, atscfg.LineCommentParentDotConfig, nil
 }
 
 // GetDSOrigins takes a map[deliveryServiceID]DeliveryService, and returns a 
map[DeliveryServiceID]OriginURI.
diff --git a/traffic_ops/ort/atstccfg/cfgfile/plugindotconfig.go 
b/traffic_ops/ort/atstccfg/cfgfile/plugindotconfig.go
index ebd30fd..2e6698f 100644
--- a/traffic_ops/ort/atstccfg/cfgfile/plugindotconfig.go
+++ b/traffic_ops/ort/atstccfg/cfgfile/plugindotconfig.go
@@ -24,7 +24,7 @@ import (
        "github.com/apache/trafficcontrol/traffic_ops/ort/atstccfg/config"
 )
 
-func GetConfigFileProfilePluginDotConfig(toData *config.TOData) (string, 
string, error) {
+func GetConfigFileProfilePluginDotConfig(toData *config.TOData) (string, 
string, string, error) {
        params := ParamsToMap(FilterParams(toData.ServerParams, 
atscfg.PluginFileName, "", "", "location"))
-       return atscfg.MakePluginDotConfig(toData.Server.Profile, params, 
toData.TOToolName, toData.TOURL), atscfg.ContentTypePluginDotConfig, nil
+       return atscfg.MakePluginDotConfig(toData.Server.Profile, params, 
toData.TOToolName, toData.TOURL), atscfg.ContentTypePluginDotConfig, 
atscfg.LineCommentPluginDotConfig, nil
 }
diff --git a/traffic_ops/ort/atstccfg/cfgfile/recordsdotconfig.go 
b/traffic_ops/ort/atstccfg/cfgfile/recordsdotconfig.go
index 58fe81e..f7064c8 100644
--- a/traffic_ops/ort/atstccfg/cfgfile/recordsdotconfig.go
+++ b/traffic_ops/ort/atstccfg/cfgfile/recordsdotconfig.go
@@ -24,7 +24,7 @@ import (
        "github.com/apache/trafficcontrol/traffic_ops/ort/atstccfg/config"
 )
 
-func GetConfigFileProfileRecordsDotConfig(toData *config.TOData) (string, 
string, error) {
+func GetConfigFileProfileRecordsDotConfig(toData *config.TOData) (string, 
string, string, error) {
        params := ParamsToMap(FilterParams(toData.ServerParams, 
atscfg.RecordsFileName, "", "", "location"))
-       return atscfg.MakeRecordsDotConfig(toData.Server.Profile, params, 
toData.TOToolName, toData.TOURL), atscfg.ContentTypeRecordsDotConfig, nil
+       return atscfg.MakeRecordsDotConfig(toData.Server.Profile, params, 
toData.TOToolName, toData.TOURL), atscfg.ContentTypeRecordsDotConfig, 
atscfg.LineCommentRecordsDotConfig, nil
 }
diff --git a/traffic_ops/ort/atstccfg/cfgfile/regexremapdotconfig.go 
b/traffic_ops/ort/atstccfg/cfgfile/regexremapdotconfig.go
index 6e718d8..fecf353 100644
--- a/traffic_ops/ort/atstccfg/cfgfile/regexremapdotconfig.go
+++ b/traffic_ops/ort/atstccfg/cfgfile/regexremapdotconfig.go
@@ -27,15 +27,15 @@ import (
        "github.com/apache/trafficcontrol/traffic_ops/ort/atstccfg/config"
 )
 
-func GetConfigFileCDNRegexRemap(toData *config.TOData, fileName string) 
(string, string, error) {
+func GetConfigFileCDNRegexRemap(toData *config.TOData, fileName string) 
(string, string, string, error) {
        configSuffix := `.config`
        if !strings.HasPrefix(fileName, atscfg.RegexRemapPrefix) || 
!strings.HasSuffix(fileName, configSuffix) {
-               return `{"alerts":[{"level":"error","text":"Error - regex remap 
file '` + fileName + `' not of the form 'regex_remap_*.config! Please file a 
bug with Traffic Control, this should never happen."}]}`, "", 
config.ErrBadRequest
+               return `{"alerts":[{"level":"error","text":"Error - regex remap 
file '` + fileName + `' not of the form 'regex_remap_*.config! Please file a 
bug with Traffic Control, this should never happen."}]}`, "", "", 
config.ErrBadRequest
        }
 
        dsName := strings.TrimSuffix(strings.TrimPrefix(fileName, 
atscfg.RegexRemapPrefix), configSuffix)
        if dsName == "" {
-               return `{"alerts":[{"level":"error","text":"Error - regex remap 
file '` + fileName + `' has no delivery service name!"}]}`, "", 
config.ErrBadRequest
+               return `{"alerts":[{"level":"error","text":"Error - regex remap 
file '` + fileName + `' has no delivery service name!"}]}`, "", "", 
config.ErrBadRequest
        }
 
        // only send the requested DS to atscfg. The atscfg.Make will work 
correctly even if we send it other DSes, but this will prevent 
atscfg.DeliveryServicesToCDNDSes from logging errors about AnyMap and Steering 
DSes without origins.
@@ -50,10 +50,10 @@ func GetConfigFileCDNRegexRemap(toData *config.TOData, 
fileName string) (string,
                ds = dsesDS
        }
        if ds.ID == nil {
-               return `{"alerts":[{"level":"error","text":"Error - delivery 
service '` + dsName + `' not found! Do you have a regex_remap_*.config location 
Parameter for a delivery service that doesn't exist?"}]}`, "", 
config.ErrNotFound
+               return `{"alerts":[{"level":"error","text":"Error - delivery 
service '` + dsName + `' not found! Do you have a regex_remap_*.config location 
Parameter for a delivery service that doesn't exist?"}]}`, "", "", 
config.ErrNotFound
        }
 
        cfgDSes := 
atscfg.DeliveryServicesToCDNDSes([]tc.DeliveryServiceNullable{ds})
 
-       return 
atscfg.MakeRegexRemapDotConfig(tc.CDNName(toData.Server.CDNName), 
toData.TOToolName, toData.TOURL, fileName, cfgDSes), 
atscfg.ContentTypeRegexRemapDotConfig, nil
+       return 
atscfg.MakeRegexRemapDotConfig(tc.CDNName(toData.Server.CDNName), 
toData.TOToolName, toData.TOURL, fileName, cfgDSes), 
atscfg.ContentTypeRegexRemapDotConfig, atscfg.LineCommentRegexRemapDotConfig, 
nil
 }
diff --git a/traffic_ops/ort/atstccfg/cfgfile/regexrevalidatedotconfig.go 
b/traffic_ops/ort/atstccfg/cfgfile/regexrevalidatedotconfig.go
index 7018f28..8df14fb 100644
--- a/traffic_ops/ort/atstccfg/cfgfile/regexrevalidatedotconfig.go
+++ b/traffic_ops/ort/atstccfg/cfgfile/regexrevalidatedotconfig.go
@@ -26,7 +26,7 @@ import (
        "github.com/apache/trafficcontrol/traffic_ops/ort/atstccfg/config"
 )
 
-func GetConfigFileCDNRegexRevalidateDotConfig(toData *config.TOData) (string, 
string, error) {
+func GetConfigFileCDNRegexRevalidateDotConfig(toData *config.TOData) (string, 
string, string, error) {
        params := map[string][]string{}
        for _, param := range toData.GlobalParams {
                if param.ConfigFile != atscfg.RegexRevalidateFileName {
@@ -52,5 +52,5 @@ func GetConfigFileCDNRegexRevalidateDotConfig(toData 
*config.TOData) (string, st
                jobs = append(jobs, job)
        }
 
-       return 
atscfg.MakeRegexRevalidateDotConfig(tc.CDNName(toData.Server.CDNName), params, 
toData.TOToolName, toData.TOURL, jobs), 
atscfg.ContentTypeRegexRevalidateDotConfig, nil
+       return 
atscfg.MakeRegexRevalidateDotConfig(tc.CDNName(toData.Server.CDNName), params, 
toData.TOToolName, toData.TOURL, jobs), 
atscfg.ContentTypeRegexRevalidateDotConfig, 
atscfg.LineCommentRegexRevalidateDotConfig, nil
 }
diff --git a/traffic_ops/ort/atstccfg/cfgfile/remapdotconfig.go 
b/traffic_ops/ort/atstccfg/cfgfile/remapdotconfig.go
index f05124f..73c35b3 100644
--- a/traffic_ops/ort/atstccfg/cfgfile/remapdotconfig.go
+++ b/traffic_ops/ort/atstccfg/cfgfile/remapdotconfig.go
@@ -32,7 +32,7 @@ import (
        "github.com/apache/trafficcontrol/traffic_ops/ort/atstccfg/config"
 )
 
-func GetConfigFileServerRemapDotConfig(toData *config.TOData) (string, string, 
error) {
+func GetConfigFileServerRemapDotConfig(toData *config.TOData) (string, string, 
string, error) {
        // TODO TOAPI add /servers?cdn=1 query param
 
        atsVersionParam := ""
@@ -49,7 +49,7 @@ func GetConfigFileServerRemapDotConfig(toData *config.TOData) 
(string, string, e
 
        atsMajorVer, err := 
atscfg.GetATSMajorVersionFromATSVersion(atsVersionParam)
        if err != nil {
-               return "", "", errors.New("getting ATS major version from 
version parameter (profile '" + toData.Server.Profile + "' configFile 'package' 
name 'trafficserver'): " + err.Error())
+               return "", "", "", errors.New("getting ATS major version from 
version parameter (profile '" + toData.Server.Profile + "' configFile 'package' 
name 'trafficserver'): " + err.Error())
        }
 
        dsIDs := map[int]struct{}{}
@@ -167,24 +167,23 @@ func GetConfigFileServerRemapDotConfig(toData 
*config.TOData) (string, string, e
                if paramValue == "STRING __HOSTNAME__" {
                        paramValue = toData.Server.HostName + "." + 
toData.Server.DomainName // TODO strings.Replace to replace all anywhere, 
instead of just an exact match?
                }
-               serverPackageParamData[paramName] = paramValue
-       }
 
-       cacheURLParams := map[string]string{}
-       for _, param := range toData.ServerParams {
-               if param.ConfigFile != atscfg.CacheURLParameterConfigFile {
-                       continue
-               }
-               if existingVal, ok := cacheURLParams[param.Name]; ok {
-                       log.Warnln("generating remap.config: server profile '" 
+ toData.Server.Profile + "' cacheurl.config has multiple parameters for '" + 
param.Name + "' - using '" + existingVal + "' and ignoring the rest!")
-                       continue
+               if val, ok := serverPackageParamData[paramName]; ok {
+                       if val < paramValue {
+                               log.Errorln("remap config generation got 
multiple parameters for server package name '" + paramName + "' - ignoring '" + 
paramValue + "'")
+                               continue
+                       } else {
+                               log.Errorln("config generation got multiple 
parameters for server package name '" + paramName + "' - ignoring '" + val + 
"'")
+                       }
                }
-               cacheURLParams[param.Name] = param.Value
+               serverPackageParamData[paramName] = paramValue
        }
 
+       cacheURLParams := ParamsToMap(FilterParams(toData.ServerParams, 
atscfg.CacheURLParameterConfigFile, "", "", ""))
+
        cacheKeyParamsWithProfiles, err := 
TCParamsToParamsWithProfiles(toData.CacheKeyParams)
        if err != nil {
-               return "", "", errors.New("decoding cache key parameter 
profiles: " + err.Error())
+               return "", "", "", errors.New("decoding cache key parameter 
profiles: " + err.Error())
        }
 
        cacheKeyParamsWithProfilesMap := 
ParameterWithProfilesToMap(cacheKeyParamsWithProfiles)
@@ -204,9 +203,13 @@ func GetConfigFileServerRemapDotConfig(toData 
*config.TOData) (string, string, e
                                if _, ok := 
dsProfilesCacheKeyConfigParams[dsProfileID]; !ok {
                                        
dsProfilesCacheKeyConfigParams[dsProfileID] = map[string]string{}
                                }
-                               if _, ok := 
dsProfilesCacheKeyConfigParams[dsProfileID][param.Name]; ok {
-                                       // TODO warn
-                                       continue
+                               if val, ok := 
dsProfilesCacheKeyConfigParams[dsProfileID][param.Name]; ok {
+                                       if val < param.Value {
+                                               log.Errorln("remap config 
generation got multiple parameters for name '" + param.Name + "' - ignoring '" 
+ param.Value + "'")
+                                               continue
+                                       } else {
+                                               log.Errorln("remap config 
generation got multiple parameters for name '" + param.Name + "' - ignoring '" 
+ val + "'")
+                                       }
                                }
                                
dsProfilesCacheKeyConfigParams[dsProfileID][param.Name] = param.Value
                        }
@@ -220,14 +223,14 @@ func GetConfigFileServerRemapDotConfig(toData 
*config.TOData) (string, string, e
        cgMap := map[string]tc.CacheGroupNullable{}
        for _, cg := range toData.CacheGroups {
                if cg.Name == nil {
-                       return "", "", errors.New("got cachegroup with nil 
name!'")
+                       return "", "", "", errors.New("got cachegroup with nil 
name!'")
                }
                cgMap[*cg.Name] = cg
        }
 
        serverCG, ok := cgMap[toData.Server.Cachegroup]
        if !ok {
-               return "", "", errors.New("server '" + toData.Server.HostName + 
"' cachegroup '" + toData.Server.Cachegroup + "' not found in CacheGroups")
+               return "", "", "", errors.New("server '" + 
toData.Server.HostName + "' cachegroup '" + toData.Server.Cachegroup + "' not 
found in CacheGroups")
        }
 
        parentCGID := -1
@@ -235,15 +238,15 @@ func GetConfigFileServerRemapDotConfig(toData 
*config.TOData) (string, string, e
        if serverCG.ParentName != nil && *serverCG.ParentName != "" {
                parentCG, ok := cgMap[*serverCG.ParentName]
                if !ok {
-                       return "", "", errors.New("server '" + 
toData.Server.HostName + "' cachegroup '" + toData.Server.Cachegroup + "' 
parent '" + *serverCG.ParentName + "' not found in CacheGroups")
+                       return "", "", "", errors.New("server '" + 
toData.Server.HostName + "' cachegroup '" + toData.Server.Cachegroup + "' 
parent '" + *serverCG.ParentName + "' not found in CacheGroups")
                }
                if parentCG.ID == nil {
-                       return "", "", errors.New("got cachegroup '" + 
*parentCG.Name + "' with nil ID!'")
+                       return "", "", "", errors.New("got cachegroup '" + 
*parentCG.Name + "' with nil ID!'")
                }
                parentCGID = *parentCG.ID
 
                if parentCG.Type == nil {
-                       return "", "", errors.New("got cachegroup '" + 
*parentCG.Name + "' with nil Type!'")
+                       return "", "", "", errors.New("got cachegroup '" + 
*parentCG.Name + "' with nil Type!'")
                }
                parentCGType = *parentCG.Type
        }
@@ -253,15 +256,15 @@ func GetConfigFileServerRemapDotConfig(toData 
*config.TOData) (string, string, e
        if serverCG.SecondaryParentName != nil && *serverCG.SecondaryParentName 
!= "" {
                parentCG, ok := cgMap[*serverCG.SecondaryParentName]
                if !ok {
-                       return "", "", errors.New("server '" + 
toData.Server.HostName + "' cachegroup '" + toData.Server.Cachegroup + "' 
secondary parent '" + *serverCG.SecondaryParentName + "' not found in 
CacheGroups")
+                       return "", "", "", errors.New("server '" + 
toData.Server.HostName + "' cachegroup '" + toData.Server.Cachegroup + "' 
secondary parent '" + *serverCG.SecondaryParentName + "' not found in 
CacheGroups")
                }
 
                if parentCG.ID == nil {
-                       return "", "", errors.New("got cachegroup '" + 
*parentCG.Name + "' with nil ID!'")
+                       return "", "", "", errors.New("got cachegroup '" + 
*parentCG.Name + "' with nil ID!'")
                }
                secondaryParentCGID = *parentCG.ID
                if parentCG.Type == nil {
-                       return "", "", errors.New("got cachegroup '" + 
*parentCG.Name + "' with nil Type!'")
+                       return "", "", "", errors.New("got cachegroup '" + 
*parentCG.Name + "' with nil Type!'")
                }
 
                secondaryParentCGType = *parentCG.Type
@@ -285,7 +288,7 @@ func GetConfigFileServerRemapDotConfig(toData 
*config.TOData) (string, string, e
                SecondaryParentCacheGroupType: secondaryParentCGType,
                Type:                          toData.Server.Type,
        }
-       return atscfg.MakeRemapDotConfig(tc.CacheName(toData.Server.HostName), 
toData.TOToolName, toData.TOURL, atsMajorVer, cacheURLParams, 
dsProfilesCacheKeyConfigParams, serverPackageParamData, serverInfo, 
remapConfigDSData), atscfg.ContentTypeRemapDotConfig, nil
+       return atscfg.MakeRemapDotConfig(tc.CacheName(toData.Server.HostName), 
toData.TOToolName, toData.TOURL, atsMajorVer, cacheURLParams, 
dsProfilesCacheKeyConfigParams, serverPackageParamData, serverInfo, 
remapConfigDSData), atscfg.ContentTypeRemapDotConfig, 
atscfg.LineCommentRemapDotConfig, nil
 }
 
 type DeliveryServiceRegexesSortByTypeThenSetNum []tc.DeliveryServiceRegex
diff --git a/traffic_ops/ort/atstccfg/cfgfile/routing.go 
b/traffic_ops/ort/atstccfg/cfgfile/routing.go
index 4845e94..58a0aaa 100644
--- a/traffic_ops/ort/atstccfg/cfgfile/routing.go
+++ b/traffic_ops/ort/atstccfg/cfgfile/routing.go
@@ -29,14 +29,14 @@ import (
        "github.com/apache/trafficcontrol/traffic_ops/ort/atstccfg/config"
 )
 
-var scopeConfigFileFuncs = map[string]func(toData *config.TOData, fileName 
string) (string, string, error){
+var scopeConfigFileFuncs = map[string]func(toData *config.TOData, fileName 
string) (string, string, string, error){
        "cdns":     GetConfigFileCDN,
        "servers":  GetConfigFileServer,
        "profiles": GetConfigFileProfile,
 }
 
 // GetConfigFile returns the text of the generated config file, the MIME 
Content Type of the config file, and any error.
-func GetConfigFile(toData *config.TOData, fileInfo 
tc.ATSConfigMetaDataConfigFile) (string, string, error) {
+func GetConfigFile(toData *config.TOData, fileInfo 
tc.ATSConfigMetaDataConfigFile) (string, string, string, error) {
        path := fileInfo.APIURI
        // TODO remove the URL path parsing. It's a legacy from when config 
files were endpoints in the meta config.
        // We should replace it with actually calling the right file and name 
directly.
@@ -47,7 +47,7 @@ func GetConfigFile(toData *config.TOData, fileInfo 
tc.ATSConfigMetaDataConfigFil
 
        pathParts := strings.Split(path, "/")
        if len(pathParts) < 8 {
-               return "", "", errors.New("unknown config file '" + path + "'")
+               return "", "", "", errors.New("unknown config file '" + path + 
"'")
        }
        scope := pathParts[3]
        resource := pathParts[4]
@@ -59,27 +59,28 @@ func GetConfigFile(toData *config.TOData, fileInfo 
tc.ATSConfigMetaDataConfigFil
                return scopeConfigFileFunc(toData, fileName)
        }
 
-       return "", "", errors.New("unknown config file '" + fileInfo.APIURI + 
"'")
+       return "", "", "", errors.New("unknown config file '" + fileInfo.APIURI 
+ "'")
 }
 
 type ConfigFilePrefixSuffixFunc struct {
        Prefix string
        Suffix string
-       Func   func(toData *config.TOData, fileName string) (string, string, 
error)
+       Func   func(toData *config.TOData, fileName string) (string, string, 
string, error)
 }
 
-func GetConfigFileCDN(toData *config.TOData, fileName string) (string, string, 
error) {
+func GetConfigFileCDN(toData *config.TOData, fileName string) (string, string, 
string, error) {
        log.Infoln("GetConfigFileCDN cdn '" + toData.Server.CDNName + "' 
fileName '" + fileName + "'")
 
        txt := ""
        contentType := ""
+       lineComment := ""
        err := error(nil)
        if getCfgFunc, ok := CDNConfigFileFuncs()[fileName]; ok {
-               txt, contentType, err = getCfgFunc(toData)
+               txt, contentType, lineComment, err = getCfgFunc(toData)
        } else {
                for _, prefixSuffixFunc := range ConfigFileCDNPrefixSuffixFuncs 
{
                        if strings.HasPrefix(fileName, prefixSuffixFunc.Prefix) 
&& strings.HasSuffix(fileName, prefixSuffixFunc.Suffix) && len(fileName) > 
len(prefixSuffixFunc.Prefix)+len(prefixSuffixFunc.Suffix) {
-                               txt, contentType, err = 
prefixSuffixFunc.Func(toData, fileName)
+                               txt, contentType, lineComment, err = 
prefixSuffixFunc.Func(toData, fileName)
                                break
                        }
                }
@@ -90,44 +91,45 @@ func GetConfigFileCDN(toData *config.TOData, fileName 
string) (string, string, e
        }
 
        if err != nil {
-               return "", "", err
+               return "", "", "", err
        }
-       return txt, contentType, nil
+       return txt, contentType, lineComment, nil
 }
 
-func GetConfigFileProfile(toData *config.TOData, fileName string) (string, 
string, error) {
+func GetConfigFileProfile(toData *config.TOData, fileName string) (string, 
string, string, error) {
        log.Infoln("GetConfigFileProfile profile '" + toData.Server.Profile + 
"' fileName '" + fileName + "'")
 
        txt := ""
        contentType := ""
+       lineComment := ""
        err := error(nil)
        if getCfgFunc, ok := ProfileConfigFileFuncs()[fileName]; ok {
-               txt, contentType, err = getCfgFunc(toData)
+               txt, contentType, lineComment, err = getCfgFunc(toData)
        } else if strings.HasPrefix(fileName, "url_sig_") && 
strings.HasSuffix(fileName, ".config") && len(fileName) > 
len("url_sig_")+len(".config") {
-               txt, contentType, err = 
GetConfigFileProfileURLSigConfig(toData, fileName)
+               txt, contentType, lineComment, err = 
GetConfigFileProfileURLSigConfig(toData, fileName)
        } else if strings.HasPrefix(fileName, "uri_signing_") && 
strings.HasSuffix(fileName, ".config") && len(fileName) > 
len("uri_signing")+len(".config") {
-               txt, contentType, err = 
GetConfigFileProfileURISigningConfig(toData, fileName)
+               txt, contentType, lineComment, err = 
GetConfigFileProfileURISigningConfig(toData, fileName)
        } else {
-               txt, contentType, err = 
GetConfigFileProfileUnknownConfig(toData, fileName)
+               txt, contentType, lineComment, err = 
GetConfigFileProfileUnknownConfig(toData, fileName)
        }
 
        if err != nil {
-               return "", "", err
+               return "", "", "", err
        }
-       return txt, contentType, nil
+       return txt, contentType, lineComment, nil
 }
 
 // ConfigFileFuncs returns a map[scope][configFile]configFileFunc.
-func ConfigFileFuncs() map[string]map[string]func(toData *config.TOData) 
(string, string, error) {
-       return map[string]map[string]func(toData *config.TOData) (string, 
string, error){
+func ConfigFileFuncs() map[string]map[string]func(toData *config.TOData) 
(string, string, string, error) {
+       return map[string]map[string]func(toData *config.TOData) (string, 
string, string, error){
                "cdns":     CDNConfigFileFuncs(),
                "servers":  ServerConfigFileFuncs(),
                "profiles": ProfileConfigFileFuncs(),
        }
 }
 
-func CDNConfigFileFuncs() map[string]func(toData *config.TOData) (string, 
string, error) {
-       return map[string]func(toData *config.TOData) (string, string, error){
+func CDNConfigFileFuncs() map[string]func(toData *config.TOData) (string, 
string, string, error) {
+       return map[string]func(toData *config.TOData) (string, string, string, 
error){
                "regex_revalidate.config": 
GetConfigFileCDNRegexRevalidateDotConfig,
                "bg_fetch.config":         GetConfigFileCDNBGFetchDotConfig,
                "ssl_multicert.config":    
GetConfigFileCDNSSLMultiCertDotConfig,
@@ -143,8 +145,8 @@ var ConfigFileCDNPrefixSuffixFuncs = 
[]ConfigFilePrefixSuffixFunc{
        {"set_dscp_", ".config", GetConfigFileCDNSetDSCP},
 }
 
-func ProfileConfigFileFuncs() map[string]func(toData *config.TOData) (string, 
string, error) {
-       return map[string]func(toData *config.TOData) (string, string, error){
+func ProfileConfigFileFuncs() map[string]func(toData *config.TOData) (string, 
string, string, error) {
+       return map[string]func(toData *config.TOData) (string, string, string, 
error){
                "12M_facts":           GetConfigFileProfile12MFacts,
                "50-ats.rules":        GetConfigFileProfileATSDotRules,
                "astats.config":       GetConfigFileProfileAstatsDotConfig,
@@ -161,8 +163,8 @@ func ProfileConfigFileFuncs() map[string]func(toData 
*config.TOData) (string, st
        }
 }
 
-func ServerConfigFileFuncs() map[string]func(toData *config.TOData) (string, 
string, error) {
-       return map[string]func(toData *config.TOData) (string, string, error){
+func ServerConfigFileFuncs() map[string]func(toData *config.TOData) (string, 
string, string, error) {
+       return map[string]func(toData *config.TOData) (string, string, string, 
error){
                "parent.config":   GetConfigFileServerParentDotConfig,
                "remap.config":    GetConfigFileServerRemapDotConfig,
                "cache.config":    GetConfigFileServerCacheDotConfig,
@@ -173,18 +175,19 @@ func ServerConfigFileFuncs() map[string]func(toData 
*config.TOData) (string, str
        }
 }
 
-func GetConfigFileServer(toData *config.TOData, fileName string) (string, 
string, error) {
+func GetConfigFileServer(toData *config.TOData, fileName string) (string, 
string, string, error) {
        log.Infoln("GetConfigFileServer server '" + toData.Server.HostName + "' 
fileName '" + fileName + "'")
        txt := ""
        contentType := ""
+       lineComment := ""
        err := error(nil)
        if getCfgFunc, ok := ServerConfigFileFuncs()[fileName]; ok {
-               txt, contentType, err = getCfgFunc(toData)
+               txt, contentType, lineComment, err = getCfgFunc(toData)
        } else {
-               txt, contentType, err = 
GetConfigFileServerUnknownConfig(toData, fileName)
+               txt, contentType, lineComment, err = 
GetConfigFileServerUnknownConfig(toData, fileName)
        }
        if err != nil {
-               return "", "", err
+               return "", "", "", err
        }
-       return txt, contentType, nil
+       return txt, contentType, lineComment, nil
 }
diff --git a/traffic_ops/ort/atstccfg/cfgfile/servercachedotconfig.go 
b/traffic_ops/ort/atstccfg/cfgfile/servercachedotconfig.go
index 2f240c6..13fb58d 100644
--- a/traffic_ops/ort/atstccfg/cfgfile/servercachedotconfig.go
+++ b/traffic_ops/ort/atstccfg/cfgfile/servercachedotconfig.go
@@ -30,13 +30,13 @@ import (
 
 const ServerCacheDotConfigIncludeInactiveDSes = false // TODO move to 
lib/go-atscfg
 
-func GetConfigFileServerCacheDotConfig(toData *config.TOData) (string, string, 
error) {
+func GetConfigFileServerCacheDotConfig(toData *config.TOData) (string, string, 
string, error) {
        // TODO TOAPI add /servers?cdn=1 query param
 
        // TODO remove this, we generated the scope, we know it's right? Or 
should we have an extra safety check?
        if !strings.HasPrefix(string(toData.Server.Type), tc.MidTypePrefix) {
                // emulates Perl
-               return "", "", errors.New("Error - incorrect file scope for 
route used.  Please use the profiles route.")
+               return "", "", "", errors.New("Error - incorrect file scope for 
route used.  Please use the profiles route.")
        }
 
        dsData := map[tc.DeliveryServiceName]atscfg.ServerCacheConfigDS{}
@@ -54,5 +54,5 @@ func GetConfigFileServerCacheDotConfig(toData *config.TOData) 
(string, string, e
 
        serverName := tc.CacheName(toData.Server.HostName)
 
-       return atscfg.MakeServerCacheDotConfig(serverName, toData.TOToolName, 
toData.TOURL, dsData), atscfg.ContentTypeCacheDotConfig, nil
+       return atscfg.MakeServerCacheDotConfig(serverName, toData.TOToolName, 
toData.TOURL, dsData), atscfg.ContentTypeCacheDotConfig, 
atscfg.LineCommentCacheDotConfig, nil
 }
diff --git a/traffic_ops/ort/atstccfg/cfgfile/serverunknownconfig.go 
b/traffic_ops/ort/atstccfg/cfgfile/serverunknownconfig.go
index 95bf799..96633e5 100644
--- a/traffic_ops/ort/atstccfg/cfgfile/serverunknownconfig.go
+++ b/traffic_ops/ort/atstccfg/cfgfile/serverunknownconfig.go
@@ -25,7 +25,8 @@ import (
        "github.com/apache/trafficcontrol/traffic_ops/ort/atstccfg/config"
 )
 
-func GetConfigFileServerUnknownConfig(toData *config.TOData, fileName string) 
(string, string, error) {
+func GetConfigFileServerUnknownConfig(toData *config.TOData, fileName string) 
(string, string, string, error) {
        params := ParamsToMultiMap(FilterParams(toData.ServerParams, fileName, 
"", "", ""))
-       return atscfg.MakeServerUnknown(tc.CacheName(toData.Server.HostName), 
toData.Server.DomainName, toData.TOToolName, toData.TOURL, params), 
atscfg.ContentTypeServerUnknownConfig, nil
+       lineComment := 
atscfg.GetServerUnknownConfigCommentType(tc.CacheName(toData.Server.HostName), 
toData.Server.DomainName, toData.TOToolName, toData.TOURL, params)
+       return atscfg.MakeServerUnknown(tc.CacheName(toData.Server.HostName), 
toData.Server.DomainName, toData.TOToolName, toData.TOURL, params), 
atscfg.ContentTypeServerUnknownConfig, lineComment, nil
 }
diff --git a/traffic_ops/ort/atstccfg/cfgfile/setdscpdotconfig.go 
b/traffic_ops/ort/atstccfg/cfgfile/setdscpdotconfig.go
index 1693ddb..6f0e6ef 100644
--- a/traffic_ops/ort/atstccfg/cfgfile/setdscpdotconfig.go
+++ b/traffic_ops/ort/atstccfg/cfgfile/setdscpdotconfig.go
@@ -27,11 +27,11 @@ import (
        "github.com/apache/trafficcontrol/traffic_ops/ort/atstccfg/config"
 )
 
-func GetConfigFileCDNSetDSCP(toData *config.TOData, fileName string) (string, 
string, error) {
+func GetConfigFileCDNSetDSCP(toData *config.TOData, fileName string) (string, 
string, string, error) {
        // TODO verify prefix, suffix, and that it's a number? Perl doesn't.
        dscpNumStr := fileName
        dscpNumStr = strings.TrimPrefix(dscpNumStr, "set_dscp_")
        dscpNumStr = strings.TrimSuffix(dscpNumStr, ".config")
 
-       return atscfg.MakeSetDSCPDotConfig(tc.CDNName(toData.Server.CDNName), 
toData.TOToolName, toData.TOURL, dscpNumStr), 
atscfg.ContentTypeSetDSCPDotConfig, nil
+       return atscfg.MakeSetDSCPDotConfig(tc.CDNName(toData.Server.CDNName), 
toData.TOToolName, toData.TOURL, dscpNumStr), 
atscfg.ContentTypeSetDSCPDotConfig, atscfg.LineCommentSetDSCPDotConfig, nil
 }
diff --git a/traffic_ops/ort/atstccfg/cfgfile/sslmulticertdotconfig.go 
b/traffic_ops/ort/atstccfg/cfgfile/sslmulticertdotconfig.go
index 4407393..f40883b 100644
--- a/traffic_ops/ort/atstccfg/cfgfile/sslmulticertdotconfig.go
+++ b/traffic_ops/ort/atstccfg/cfgfile/sslmulticertdotconfig.go
@@ -25,8 +25,8 @@ import (
        "github.com/apache/trafficcontrol/traffic_ops/ort/atstccfg/config"
 )
 
-func GetConfigFileCDNSSLMultiCertDotConfig(toData *config.TOData) (string, 
string, error) {
+func GetConfigFileCDNSSLMultiCertDotConfig(toData *config.TOData) (string, 
string, string, error) {
        cfgDSes := 
atscfg.DeliveryServicesToSSLMultiCertDSes(toData.DeliveryServices)
 
-       return 
atscfg.MakeSSLMultiCertDotConfig(tc.CDNName(toData.Server.CDNName), 
toData.TOToolName, toData.TOURL, cfgDSes), 
atscfg.ContentTypeSSLMultiCertDotConfig, nil
+       return 
atscfg.MakeSSLMultiCertDotConfig(tc.CDNName(toData.Server.CDNName), 
toData.TOToolName, toData.TOURL, cfgDSes), 
atscfg.ContentTypeSSLMultiCertDotConfig, 
atscfg.LineCommentSSLMultiCertDotConfig, nil
 }
diff --git a/traffic_ops/ort/atstccfg/cfgfile/storagedotconfig.go 
b/traffic_ops/ort/atstccfg/cfgfile/storagedotconfig.go
index 8bd169d..01a824b 100644
--- a/traffic_ops/ort/atstccfg/cfgfile/storagedotconfig.go
+++ b/traffic_ops/ort/atstccfg/cfgfile/storagedotconfig.go
@@ -26,7 +26,7 @@ import (
 
 const StorageFileName = "storage.config"
 
-func GetConfigFileProfileStorageDotConfig(toData *config.TOData) (string, 
string, error) {
+func GetConfigFileProfileStorageDotConfig(toData *config.TOData) (string, 
string, string, error) {
        params := ParamsToMap(FilterParams(toData.ServerParams, 
StorageFileName, "", "", "location"))
-       return atscfg.MakeStorageDotConfig(toData.Server.Profile, params, 
toData.TOToolName, toData.TOURL), atscfg.ContentTypeStorageDotConfig, nil
+       return atscfg.MakeStorageDotConfig(toData.Server.Profile, params, 
toData.TOToolName, toData.TOURL), atscfg.ContentTypeStorageDotConfig, 
atscfg.LineCommentStorageDotConfig, nil
 }
diff --git a/traffic_ops/ort/atstccfg/cfgfile/sysctldotconf.go 
b/traffic_ops/ort/atstccfg/cfgfile/sysctldotconf.go
index 44e9ae5..6f5abcc 100644
--- a/traffic_ops/ort/atstccfg/cfgfile/sysctldotconf.go
+++ b/traffic_ops/ort/atstccfg/cfgfile/sysctldotconf.go
@@ -24,18 +24,7 @@ import (
        "github.com/apache/trafficcontrol/traffic_ops/ort/atstccfg/config"
 )
 
-func GetConfigFileProfileSysCtlDotConf(toData *config.TOData) (string, string, 
error) {
-       paramData := map[string]string{}
-       // TODO add configFile query param to profile/parameters endpoint, to 
only get needed data
-       for _, param := range toData.ServerParams {
-               if param.ConfigFile != atscfg.SysctlFileName {
-                       continue
-               }
-               if param.Name == "location" {
-                       continue
-               }
-               paramData[param.Name] = param.Value
-       }
-
-       return atscfg.MakeSysCtlDotConf(toData.Server.Profile, paramData, 
toData.TOToolName, toData.TOURL), atscfg.ContentTypeSysctlDotConf, nil
+func GetConfigFileProfileSysCtlDotConf(toData *config.TOData) (string, string, 
string, error) {
+       paramData := ParamsToMap(FilterParams(toData.ServerParams, 
atscfg.SysctlFileName, "", "", "location"))
+       return atscfg.MakeSysCtlDotConf(toData.Server.Profile, paramData, 
toData.TOToolName, toData.TOURL), atscfg.ContentTypeSysctlDotConf, 
atscfg.LineCommentSysctlDotConf, nil
 }
diff --git a/traffic_ops/ort/atstccfg/cfgfile/unknownconfig.go 
b/traffic_ops/ort/atstccfg/cfgfile/unknownconfig.go
index e6b75d4..2dd3e18 100644
--- a/traffic_ops/ort/atstccfg/cfgfile/unknownconfig.go
+++ b/traffic_ops/ort/atstccfg/cfgfile/unknownconfig.go
@@ -24,7 +24,7 @@ import (
        "github.com/apache/trafficcontrol/traffic_ops/ort/atstccfg/config"
 )
 
-func GetConfigFileProfileUnknownConfig(toData *config.TOData, fileName string) 
(string, string, error) {
+func GetConfigFileProfileUnknownConfig(toData *config.TOData, fileName string) 
(string, string, string, error) {
        inScope := false
        for _, scopeParam := range toData.ScopeParams {
                if scopeParam.ConfigFile != fileName {
@@ -37,8 +37,11 @@ func GetConfigFileProfileUnknownConfig(toData 
*config.TOData, fileName string) (
                break
        }
        if !inScope {
-               return `{"alerts":[{"level":"error","text":"Error - incorrect 
file scope for route used.  Please use the servers route."}]}`, "", 
config.ErrBadRequest
+               return `{"alerts":[{"level":"error","text":"Error - incorrect 
file scope for route used.  Please use the servers route."}]}`, "", "", 
config.ErrBadRequest
        }
        params := ParamsToMap(FilterParams(toData.ServerParams, fileName, "", 
"", "location"))
-       return atscfg.MakeUnknownConfig(toData.Server.Profile, params, 
toData.TOToolName, toData.TOURL), atscfg.ContentTypeUnknownConfig, nil
+
+       commentType := 
atscfg.GetUnknownConfigCommentType(toData.Server.Profile, params, 
toData.TOToolName, toData.TOURL)
+       txt := atscfg.MakeUnknownConfig(toData.Server.Profile, params, 
toData.TOToolName, toData.TOURL)
+       return txt, atscfg.ContentTypeUnknownConfig, commentType, nil
 }
diff --git a/traffic_ops/ort/atstccfg/cfgfile/urisigningconfig.go 
b/traffic_ops/ort/atstccfg/cfgfile/urisigningconfig.go
index 2136f51..8756b77 100644
--- a/traffic_ops/ort/atstccfg/cfgfile/urisigningconfig.go
+++ b/traffic_ops/ort/atstccfg/cfgfile/urisigningconfig.go
@@ -28,19 +28,19 @@ import (
        "github.com/apache/trafficcontrol/traffic_ops/ort/atstccfg/config"
 )
 
-func GetConfigFileProfileURISigningConfig(toData *config.TOData, fileName 
string) (string, string, error) {
+func GetConfigFileProfileURISigningConfig(toData *config.TOData, fileName 
string) (string, string, string, error) {
        dsName := GetDSFromURISigningConfigFileName(fileName)
        if dsName == "" {
                // extra safety, this should never happen, the routing 
shouldn't get here
-               return "", "", errors.New("getting ds name: malformed config 
file '" + fileName + "'")
+               return "", "", "", errors.New("getting ds name: malformed 
config file '" + fileName + "'")
        }
 
        uriSigningKeys, ok := 
toData.URISigningKeys[tc.DeliveryServiceName(dsName)]
        if !ok {
-               return "", "", errors.New("no keys fetched for ds '" + dsName + 
"!")
+               return "", "", "", errors.New("no keys fetched for ds '" + 
dsName + "!")
        }
 
-       return atscfg.MakeURISigningConfig(uriSigningKeys), 
atscfg.ContentTypeURISigningDotConfig, nil
+       return atscfg.MakeURISigningConfig(uriSigningKeys), 
atscfg.ContentTypeURISigningDotConfig, atscfg.LineCommentURISigningDotConfig, 
nil
 }
 
 // GetDSFromURISigningConfigFileName returns the DS of a URI Signing config 
file name.
diff --git a/traffic_ops/ort/atstccfg/cfgfile/urlsigconfig.go 
b/traffic_ops/ort/atstccfg/cfgfile/urlsigconfig.go
index f19a32d..eb689f2 100644
--- a/traffic_ops/ort/atstccfg/cfgfile/urlsigconfig.go
+++ b/traffic_ops/ort/atstccfg/cfgfile/urlsigconfig.go
@@ -28,31 +28,21 @@ import (
        "github.com/apache/trafficcontrol/traffic_ops/ort/atstccfg/config"
 )
 
-func GetConfigFileProfileURLSigConfig(toData *config.TOData, fileName string) 
(string, string, error) {
-       paramData := map[string]string{}
-       // TODO add configFile query param to profile/parameters endpoint, to 
only get needed data
-       for _, param := range toData.ServerParams {
-               if param.ConfigFile != fileName {
-                       continue
-               }
-               if param.Name == "location" {
-                       continue
-               }
-               paramData[param.Name] = param.Value
-       }
+func GetConfigFileProfileURLSigConfig(toData *config.TOData, fileName string) 
(string, string, string, error) {
+       paramData := ParamsToMap(FilterParams(toData.ServerParams, fileName, 
"", "", "location"))
 
        dsName := GetDSFromURLSigConfigFileName(fileName)
        if dsName == "" {
                // extra safety, this should never happen, the routing 
shouldn't get here
-               return "", "", errors.New("getting ds name: malformed config 
file '" + fileName + "'")
+               return "", "", "", errors.New("getting ds name: malformed 
config file '" + fileName + "'")
        }
 
        urlSigKeys, ok := toData.URLSigKeys[tc.DeliveryServiceName(dsName)]
        if !ok {
-               return "", "", errors.New("no keys fetched for ds '" + dsName + 
"!")
+               return "", "", "", errors.New("no keys fetched for ds '" + 
dsName + "!")
        }
 
-       return atscfg.MakeURLSigConfig(toData.Server.Profile, urlSigKeys, 
paramData, toData.TOToolName, toData.TOURL), atscfg.ContentTypeParentDotConfig, 
nil
+       return atscfg.MakeURLSigConfig(toData.Server.Profile, urlSigKeys, 
paramData, toData.TOToolName, toData.TOURL), atscfg.ContentTypeURLSig, 
atscfg.LineCommentURLSig, nil
 }
 
 // GetDSFromURLSigConfigFileName returns the DS of a URLSig config file name.
diff --git a/traffic_ops/ort/atstccfg/cfgfile/volumedotconfig.go 
b/traffic_ops/ort/atstccfg/cfgfile/volumedotconfig.go
index e5ad385..71f457d 100644
--- a/traffic_ops/ort/atstccfg/cfgfile/volumedotconfig.go
+++ b/traffic_ops/ort/atstccfg/cfgfile/volumedotconfig.go
@@ -26,7 +26,7 @@ import (
 
 const VolumeFileName = StorageFileName
 
-func GetConfigFileProfileVolumeDotConfig(toData *config.TOData) (string, 
string, error) {
+func GetConfigFileProfileVolumeDotConfig(toData *config.TOData) (string, 
string, string, error) {
        params := ParamsToMap(FilterParams(toData.ServerParams, VolumeFileName, 
"", "", ""))
-       return atscfg.MakeVolumeDotConfig(toData.Server.Profile, params, 
toData.TOToolName, toData.TOURL), atscfg.ContentTypeVolumeDotConfig, nil
+       return atscfg.MakeVolumeDotConfig(toData.Server.Profile, params, 
toData.TOToolName, toData.TOURL), atscfg.ContentTypeVolumeDotConfig, 
atscfg.LineCommentVolumeDotConfig, nil
 }
diff --git a/traffic_ops/ort/atstccfg/config/config.go 
b/traffic_ops/ort/atstccfg/config/config.go
index f8303cc..3b87f18 100644
--- a/traffic_ops/ort/atstccfg/config/config.go
+++ b/traffic_ops/ort/atstccfg/config/config.go
@@ -203,8 +203,21 @@ type ATSConfigFile struct {
        tc.ATSConfigMetaDataConfigFile
        Text        string
        ContentType string
+       LineComment string
 }
 
+// ATSConfigFiles implements sort.Interface and sorts by the Location and then 
FileNameOnDisk, i.e. the full file path.
+type ATSConfigFiles []ATSConfigFile
+
+func (fs ATSConfigFiles) Len() int { return len(fs) }
+func (fs ATSConfigFiles) Less(i, j int) bool {
+       if fs[i].Location != fs[j].Location {
+               return fs[i].Location < fs[j].Location
+       }
+       return fs[i].FileNameOnDisk < fs[j].FileNameOnDisk
+}
+func (fs ATSConfigFiles) Swap(i, j int) { fs[i], fs[j] = fs[j], fs[i] }
+
 // TOData is the Traffic Ops data needed to generate configs.
 // See each field for details on the data required.
 // - If a field says 'must', the creation of TOData is guaranteed to do so, 
and users of the struct may rely on that.
diff --git a/traffic_ops/ort/atstccfg/plugin/hello_world.go 
b/traffic_ops/ort/atstccfg/plugin/hello_world.go
index f25fda9..0808692 100644
--- a/traffic_ops/ort/atstccfg/plugin/hello_world.go
+++ b/traffic_ops/ort/atstccfg/plugin/hello_world.go
@@ -41,6 +41,7 @@ func hello(d ModifyFilesData) []config.ATSConfigFile {
 
        fi.Text = "Hello, World!\n"
        fi.ContentType = "text/plain"
+       fi.LineComment = ""
        fi.FileNameOnDisk = "hello.txt"
        fi.Location = "/opt/trafficserver/etc/trafficserver/"
 
diff --git a/traffic_ops/ort/atstccfg/plugin/plugin_test.go 
b/traffic_ops/ort/atstccfg/plugin/plugin_test.go
index 68ffed3..2285876 100644
--- a/traffic_ops/ort/atstccfg/plugin/plugin_test.go
+++ b/traffic_ops/ort/atstccfg/plugin/plugin_test.go
@@ -34,6 +34,7 @@ func TestPlugin(t *testing.T) {
                        fi := config.ATSConfigFile{}
                        fi.Text = "testfile\n"
                        fi.ContentType = "text/plain"
+                       fi.LineComment = ""
                        fi.FileNameOnDisk = "testfile.txt"
                        fi.Location = "/opt/trafficserver/etc/trafficserver/"
                        d.Files = append(d.Files, fi)

Reply via email to