interpolation.go 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170
  1. package config
  2. import (
  3. "bytes"
  4. "fmt"
  5. "strings"
  6. "github.com/Sirupsen/logrus"
  7. )
  8. func isNum(c uint8) bool {
  9. return c >= '0' && c <= '9'
  10. }
  11. func validVariableNameChar(c uint8) bool {
  12. return c == '_' ||
  13. c >= 'A' && c <= 'Z' ||
  14. c >= 'a' && c <= 'z' ||
  15. isNum(c)
  16. }
  17. func parseVariable(line string, pos int, mapping func(string) string) (string, int, bool) {
  18. var buffer bytes.Buffer
  19. for ; pos < len(line); pos++ {
  20. c := line[pos]
  21. switch {
  22. case validVariableNameChar(c):
  23. buffer.WriteByte(c)
  24. default:
  25. return mapping(buffer.String()), pos - 1, true
  26. }
  27. }
  28. return mapping(buffer.String()), pos, true
  29. }
  30. func parseVariableWithBraces(line string, pos int, mapping func(string) string) (string, int, bool) {
  31. var buffer bytes.Buffer
  32. for ; pos < len(line); pos++ {
  33. c := line[pos]
  34. switch {
  35. case c == '}':
  36. bufferString := buffer.String()
  37. if bufferString == "" {
  38. return "", 0, false
  39. }
  40. return mapping(buffer.String()), pos, true
  41. case validVariableNameChar(c):
  42. buffer.WriteByte(c)
  43. default:
  44. return "", 0, false
  45. }
  46. }
  47. return "", 0, false
  48. }
  49. func parseInterpolationExpression(line string, pos int, mapping func(string) string) (string, int, bool) {
  50. c := line[pos]
  51. switch {
  52. case c == '$':
  53. return "$", pos, true
  54. case c == '{':
  55. return parseVariableWithBraces(line, pos+1, mapping)
  56. case !isNum(c) && validVariableNameChar(c):
  57. // Variables can't start with a number
  58. return parseVariable(line, pos, mapping)
  59. default:
  60. return "", 0, false
  61. }
  62. }
  63. func parseLine(line string, mapping func(string) string) (string, bool) {
  64. var buffer bytes.Buffer
  65. for pos := 0; pos < len(line); pos++ {
  66. c := line[pos]
  67. switch {
  68. case c == '$':
  69. var replaced string
  70. var success bool
  71. replaced, pos, success = parseInterpolationExpression(line, pos+1, mapping)
  72. if !success {
  73. return "", false
  74. }
  75. buffer.WriteString(replaced)
  76. default:
  77. buffer.WriteByte(c)
  78. }
  79. }
  80. return buffer.String(), true
  81. }
  82. func parseConfig(option, service string, data *interface{}, mapping func(string) string) error {
  83. switch typedData := (*data).(type) {
  84. case string:
  85. var success bool
  86. *data, success = parseLine(typedData, mapping)
  87. if !success {
  88. return fmt.Errorf("Invalid interpolation format for \"%s\" option in service \"%s\": \"%s\"", option, service, typedData)
  89. }
  90. case []interface{}:
  91. for k, v := range typedData {
  92. err := parseConfig(option, service, &v, mapping)
  93. if err != nil {
  94. return err
  95. }
  96. typedData[k] = v
  97. }
  98. case map[interface{}]interface{}:
  99. for k, v := range typedData {
  100. err := parseConfig(option, service, &v, mapping)
  101. if err != nil {
  102. return err
  103. }
  104. typedData[k] = v
  105. }
  106. }
  107. return nil
  108. }
  109. // Interpolate replaces variables in the raw map representation of the project file
  110. func Interpolate(environmentLookup EnvironmentLookup, config *RawServiceMap) error {
  111. for k, v := range *config {
  112. for k2, v2 := range v {
  113. err := parseConfig(k2, k, &v2, func(s string) string {
  114. values := environmentLookup.Lookup(s, k, nil)
  115. if len(values) == 0 {
  116. logrus.Warnf("The %s variable is not set. Substituting a blank string.", s)
  117. return ""
  118. }
  119. // Use first result if many are given
  120. value := values[0]
  121. // Environment variables come in key=value format
  122. // Return everything past first '='
  123. return strings.SplitN(value, "=", 2)[1]
  124. })
  125. if err != nil {
  126. return err
  127. }
  128. (*config)[k][k2] = v2
  129. }
  130. }
  131. return nil
  132. }