github.com/biggiemac/terraform@v0.6.12-0.20160217180759-34b7665af0d6/config/interpolate_funcs.go (about)

     1  package config
     2  
     3  import (
     4  	"bytes"
     5  	"crypto/sha1"
     6  	"crypto/sha256"
     7  	"encoding/base64"
     8  	"encoding/hex"
     9  	"errors"
    10  	"fmt"
    11  	"io/ioutil"
    12  	"net"
    13  	"regexp"
    14  	"sort"
    15  	"strconv"
    16  	"strings"
    17  
    18  	"github.com/apparentlymart/go-cidr/cidr"
    19  	"github.com/hashicorp/hil/ast"
    20  	"github.com/mitchellh/go-homedir"
    21  )
    22  
    23  // Funcs is the mapping of built-in functions for configuration.
    24  func Funcs() map[string]ast.Function {
    25  	return map[string]ast.Function{
    26  		"base64sha256": interpolationFuncBase64Sha256(),
    27  		"cidrhost":     interpolationFuncCidrHost(),
    28  		"cidrnetmask":  interpolationFuncCidrNetmask(),
    29  		"cidrsubnet":   interpolationFuncCidrSubnet(),
    30  		"coalesce":     interpolationFuncCoalesce(),
    31  		"compact":      interpolationFuncCompact(),
    32  		"concat":       interpolationFuncConcat(),
    33  		"element":      interpolationFuncElement(),
    34  		"file":         interpolationFuncFile(),
    35  		"format":       interpolationFuncFormat(),
    36  		"formatlist":   interpolationFuncFormatList(),
    37  		"index":        interpolationFuncIndex(),
    38  		"join":         interpolationFuncJoin(),
    39  		"length":       interpolationFuncLength(),
    40  		"lower":        interpolationFuncLower(),
    41  		"replace":      interpolationFuncReplace(),
    42  		"signum":       interpolationFuncSignum(),
    43  		"split":        interpolationFuncSplit(),
    44  		"sha1":         interpolationFuncSha1(),
    45  		"sha256":       interpolationFuncSha256(),
    46  		"trimspace":    interpolationFuncTrimSpace(),
    47  		"base64encode": interpolationFuncBase64Encode(),
    48  		"base64decode": interpolationFuncBase64Decode(),
    49  		"upper":        interpolationFuncUpper(),
    50  	}
    51  }
    52  
    53  // interpolationFuncCompact strips a list of multi-variable values
    54  // (e.g. as returned by "split") of any empty strings.
    55  func interpolationFuncCompact() ast.Function {
    56  	return ast.Function{
    57  		ArgTypes:   []ast.Type{ast.TypeString},
    58  		ReturnType: ast.TypeString,
    59  		Variadic:   false,
    60  		Callback: func(args []interface{}) (interface{}, error) {
    61  			if !IsStringList(args[0].(string)) {
    62  				return args[0].(string), nil
    63  			}
    64  			return StringList(args[0].(string)).Compact().String(), nil
    65  		},
    66  	}
    67  }
    68  
    69  // interpolationFuncCidrHost implements the "cidrhost" function that
    70  // fills in the host part of a CIDR range address to create a single
    71  // host address
    72  func interpolationFuncCidrHost() ast.Function {
    73  	return ast.Function{
    74  		ArgTypes: []ast.Type{
    75  			ast.TypeString, // starting CIDR mask
    76  			ast.TypeInt,    // host number to insert
    77  		},
    78  		ReturnType: ast.TypeString,
    79  		Variadic:   false,
    80  		Callback: func(args []interface{}) (interface{}, error) {
    81  			hostNum := args[1].(int)
    82  			_, network, err := net.ParseCIDR(args[0].(string))
    83  			if err != nil {
    84  				return nil, fmt.Errorf("invalid CIDR expression: %s", err)
    85  			}
    86  
    87  			ip, err := cidr.Host(network, hostNum)
    88  			if err != nil {
    89  				return nil, err
    90  			}
    91  
    92  			return ip.String(), nil
    93  		},
    94  	}
    95  }
    96  
    97  // interpolationFuncCidrNetmask implements the "cidrnetmask" function
    98  // that returns the subnet mask in IP address notation.
    99  func interpolationFuncCidrNetmask() ast.Function {
   100  	return ast.Function{
   101  		ArgTypes: []ast.Type{
   102  			ast.TypeString, // CIDR mask
   103  		},
   104  		ReturnType: ast.TypeString,
   105  		Variadic:   false,
   106  		Callback: func(args []interface{}) (interface{}, error) {
   107  			_, network, err := net.ParseCIDR(args[0].(string))
   108  			if err != nil {
   109  				return nil, fmt.Errorf("invalid CIDR expression: %s", err)
   110  			}
   111  
   112  			return net.IP(network.Mask).String(), nil
   113  		},
   114  	}
   115  }
   116  
   117  // interpolationFuncCidrSubnet implements the "cidrsubnet" function that
   118  // adds an additional subnet of the given length onto an existing
   119  // IP block expressed in CIDR notation.
   120  func interpolationFuncCidrSubnet() ast.Function {
   121  	return ast.Function{
   122  		ArgTypes: []ast.Type{
   123  			ast.TypeString, // starting CIDR mask
   124  			ast.TypeInt,    // number of bits to extend the prefix
   125  			ast.TypeInt,    // network number to append to the prefix
   126  		},
   127  		ReturnType: ast.TypeString,
   128  		Variadic:   false,
   129  		Callback: func(args []interface{}) (interface{}, error) {
   130  			extraBits := args[1].(int)
   131  			subnetNum := args[2].(int)
   132  			_, network, err := net.ParseCIDR(args[0].(string))
   133  			if err != nil {
   134  				return nil, fmt.Errorf("invalid CIDR expression: %s", err)
   135  			}
   136  
   137  			// For portability with 32-bit systems where the subnet number
   138  			// will be a 32-bit int, we only allow extension of 32 bits in
   139  			// one call even if we're running on a 64-bit machine.
   140  			// (Of course, this is significant only for IPv6.)
   141  			if extraBits > 32 {
   142  				return nil, fmt.Errorf("may not extend prefix by more than 32 bits")
   143  			}
   144  
   145  			newNetwork, err := cidr.Subnet(network, extraBits, subnetNum)
   146  			if err != nil {
   147  				return nil, err
   148  			}
   149  
   150  			return newNetwork.String(), nil
   151  		},
   152  	}
   153  }
   154  
   155  // interpolationFuncCoalesce implements the "coalesce" function that
   156  // returns the first non null / empty string from the provided input
   157  func interpolationFuncCoalesce() ast.Function {
   158  	return ast.Function{
   159  		ArgTypes:     []ast.Type{ast.TypeString},
   160  		ReturnType:   ast.TypeString,
   161  		Variadic:     true,
   162  		VariadicType: ast.TypeString,
   163  		Callback: func(args []interface{}) (interface{}, error) {
   164  			if len(args) < 2 {
   165  				return nil, fmt.Errorf("must provide at least two arguments")
   166  			}
   167  			for _, arg := range args {
   168  				argument := arg.(string)
   169  
   170  				if argument != "" {
   171  					return argument, nil
   172  				}
   173  			}
   174  			return "", nil
   175  		},
   176  	}
   177  }
   178  
   179  // interpolationFuncConcat implements the "concat" function that
   180  // concatenates multiple strings. This isn't actually necessary anymore
   181  // since our language supports string concat natively, but for backwards
   182  // compat we do this.
   183  func interpolationFuncConcat() ast.Function {
   184  	return ast.Function{
   185  		ArgTypes:     []ast.Type{ast.TypeString},
   186  		ReturnType:   ast.TypeString,
   187  		Variadic:     true,
   188  		VariadicType: ast.TypeString,
   189  		Callback: func(args []interface{}) (interface{}, error) {
   190  			var b bytes.Buffer
   191  			var finalList []string
   192  
   193  			var isDeprecated = true
   194  
   195  			for _, arg := range args {
   196  				argument := arg.(string)
   197  
   198  				if len(argument) == 0 {
   199  					continue
   200  				}
   201  
   202  				if IsStringList(argument) {
   203  					isDeprecated = false
   204  					finalList = append(finalList, StringList(argument).Slice()...)
   205  				} else {
   206  					finalList = append(finalList, argument)
   207  				}
   208  
   209  				// Deprecated concat behaviour
   210  				b.WriteString(argument)
   211  			}
   212  
   213  			if isDeprecated {
   214  				return b.String(), nil
   215  			}
   216  
   217  			return NewStringList(finalList).String(), nil
   218  		},
   219  	}
   220  }
   221  
   222  // interpolationFuncFile implements the "file" function that allows
   223  // loading contents from a file.
   224  func interpolationFuncFile() ast.Function {
   225  	return ast.Function{
   226  		ArgTypes:   []ast.Type{ast.TypeString},
   227  		ReturnType: ast.TypeString,
   228  		Callback: func(args []interface{}) (interface{}, error) {
   229  			path, err := homedir.Expand(args[0].(string))
   230  			if err != nil {
   231  				return "", err
   232  			}
   233  			data, err := ioutil.ReadFile(path)
   234  			if err != nil {
   235  				return "", err
   236  			}
   237  
   238  			return string(data), nil
   239  		},
   240  	}
   241  }
   242  
   243  // interpolationFuncFormat implements the "format" function that does
   244  // string formatting.
   245  func interpolationFuncFormat() ast.Function {
   246  	return ast.Function{
   247  		ArgTypes:     []ast.Type{ast.TypeString},
   248  		Variadic:     true,
   249  		VariadicType: ast.TypeAny,
   250  		ReturnType:   ast.TypeString,
   251  		Callback: func(args []interface{}) (interface{}, error) {
   252  			format := args[0].(string)
   253  			return fmt.Sprintf(format, args[1:]...), nil
   254  		},
   255  	}
   256  }
   257  
   258  // interpolationFuncFormatList implements the "formatlist" function that does
   259  // string formatting on lists.
   260  func interpolationFuncFormatList() ast.Function {
   261  	return ast.Function{
   262  		ArgTypes:     []ast.Type{ast.TypeString},
   263  		Variadic:     true,
   264  		VariadicType: ast.TypeAny,
   265  		ReturnType:   ast.TypeString,
   266  		Callback: func(args []interface{}) (interface{}, error) {
   267  			// Make a copy of the variadic part of args
   268  			// to avoid modifying the original.
   269  			varargs := make([]interface{}, len(args)-1)
   270  			copy(varargs, args[1:])
   271  
   272  			// Convert arguments that are lists into slices.
   273  			// Confirm along the way that all lists have the same length (n).
   274  			var n int
   275  			for i := 1; i < len(args); i++ {
   276  				s, ok := args[i].(string)
   277  				if !ok {
   278  					continue
   279  				}
   280  				if !IsStringList(s) {
   281  					continue
   282  				}
   283  
   284  				parts := StringList(s).Slice()
   285  
   286  				// otherwise the list is sent down to be indexed
   287  				varargs[i-1] = parts
   288  
   289  				// Check length
   290  				if n == 0 {
   291  					// first list we've seen
   292  					n = len(parts)
   293  					continue
   294  				}
   295  				if n != len(parts) {
   296  					return nil, fmt.Errorf("format: mismatched list lengths: %d != %d", n, len(parts))
   297  				}
   298  			}
   299  
   300  			if n == 0 {
   301  				return nil, errors.New("no lists in arguments to formatlist")
   302  			}
   303  
   304  			// Do the formatting.
   305  			format := args[0].(string)
   306  
   307  			// Generate a list of formatted strings.
   308  			list := make([]string, n)
   309  			fmtargs := make([]interface{}, len(varargs))
   310  			for i := 0; i < n; i++ {
   311  				for j, arg := range varargs {
   312  					switch arg := arg.(type) {
   313  					default:
   314  						fmtargs[j] = arg
   315  					case []string:
   316  						fmtargs[j] = arg[i]
   317  					}
   318  				}
   319  				list[i] = fmt.Sprintf(format, fmtargs...)
   320  			}
   321  			return NewStringList(list).String(), nil
   322  		},
   323  	}
   324  }
   325  
   326  // interpolationFuncIndex implements the "index" function that allows one to
   327  // find the index of a specific element in a list
   328  func interpolationFuncIndex() ast.Function {
   329  	return ast.Function{
   330  		ArgTypes:   []ast.Type{ast.TypeString, ast.TypeString},
   331  		ReturnType: ast.TypeInt,
   332  		Callback: func(args []interface{}) (interface{}, error) {
   333  			haystack := StringList(args[0].(string)).Slice()
   334  			needle := args[1].(string)
   335  			for index, element := range haystack {
   336  				if needle == element {
   337  					return index, nil
   338  				}
   339  			}
   340  			return nil, fmt.Errorf("Could not find '%s' in '%s'", needle, haystack)
   341  		},
   342  	}
   343  }
   344  
   345  // interpolationFuncJoin implements the "join" function that allows
   346  // multi-variable values to be joined by some character.
   347  func interpolationFuncJoin() ast.Function {
   348  	return ast.Function{
   349  		ArgTypes:   []ast.Type{ast.TypeString, ast.TypeString},
   350  		ReturnType: ast.TypeString,
   351  		Callback: func(args []interface{}) (interface{}, error) {
   352  			var list []string
   353  			for _, arg := range args[1:] {
   354  				parts := StringList(arg.(string)).Slice()
   355  				list = append(list, parts...)
   356  			}
   357  
   358  			return strings.Join(list, args[0].(string)), nil
   359  		},
   360  	}
   361  }
   362  
   363  // interpolationFuncReplace implements the "replace" function that does
   364  // string replacement.
   365  func interpolationFuncReplace() ast.Function {
   366  	return ast.Function{
   367  		ArgTypes:   []ast.Type{ast.TypeString, ast.TypeString, ast.TypeString},
   368  		ReturnType: ast.TypeString,
   369  		Callback: func(args []interface{}) (interface{}, error) {
   370  			s := args[0].(string)
   371  			search := args[1].(string)
   372  			replace := args[2].(string)
   373  
   374  			// We search/replace using a regexp if the string is surrounded
   375  			// in forward slashes.
   376  			if len(search) > 1 && search[0] == '/' && search[len(search)-1] == '/' {
   377  				re, err := regexp.Compile(search[1 : len(search)-1])
   378  				if err != nil {
   379  					return nil, err
   380  				}
   381  
   382  				return re.ReplaceAllString(s, replace), nil
   383  			}
   384  
   385  			return strings.Replace(s, search, replace, -1), nil
   386  		},
   387  	}
   388  }
   389  
   390  func interpolationFuncLength() ast.Function {
   391  	return ast.Function{
   392  		ArgTypes:   []ast.Type{ast.TypeString},
   393  		ReturnType: ast.TypeInt,
   394  		Variadic:   false,
   395  		Callback: func(args []interface{}) (interface{}, error) {
   396  			if !IsStringList(args[0].(string)) {
   397  				return len(args[0].(string)), nil
   398  			}
   399  
   400  			length := 0
   401  			for _, arg := range args {
   402  				length += StringList(arg.(string)).Length()
   403  			}
   404  			return length, nil
   405  		},
   406  	}
   407  }
   408  
   409  func interpolationFuncSignum() ast.Function {
   410  	return ast.Function{
   411  		ArgTypes:   []ast.Type{ast.TypeInt},
   412  		ReturnType: ast.TypeInt,
   413  		Variadic:   false,
   414  		Callback: func(args []interface{}) (interface{}, error) {
   415  			num := args[0].(int)
   416  			switch {
   417  			case num < 0:
   418  				return -1, nil
   419  			case num > 0:
   420  				return +1, nil
   421  			default:
   422  				return 0, nil
   423  			}
   424  		},
   425  	}
   426  }
   427  
   428  // interpolationFuncSplit implements the "split" function that allows
   429  // strings to split into multi-variable values
   430  func interpolationFuncSplit() ast.Function {
   431  	return ast.Function{
   432  		ArgTypes:   []ast.Type{ast.TypeString, ast.TypeString},
   433  		ReturnType: ast.TypeString,
   434  		Callback: func(args []interface{}) (interface{}, error) {
   435  			sep := args[0].(string)
   436  			s := args[1].(string)
   437  			return NewStringList(strings.Split(s, sep)).String(), nil
   438  		},
   439  	}
   440  }
   441  
   442  // interpolationFuncLookup implements the "lookup" function that allows
   443  // dynamic lookups of map types within a Terraform configuration.
   444  func interpolationFuncLookup(vs map[string]ast.Variable) ast.Function {
   445  	return ast.Function{
   446  		ArgTypes:   []ast.Type{ast.TypeString, ast.TypeString},
   447  		ReturnType: ast.TypeString,
   448  		Callback: func(args []interface{}) (interface{}, error) {
   449  			k := fmt.Sprintf("var.%s.%s", args[0].(string), args[1].(string))
   450  			v, ok := vs[k]
   451  			if !ok {
   452  				return "", fmt.Errorf(
   453  					"lookup in '%s' failed to find '%s'",
   454  					args[0].(string), args[1].(string))
   455  			}
   456  			if v.Type != ast.TypeString {
   457  				return "", fmt.Errorf(
   458  					"lookup in '%s' for '%s' has bad type %s",
   459  					args[0].(string), args[1].(string), v.Type)
   460  			}
   461  
   462  			return v.Value.(string), nil
   463  		},
   464  	}
   465  }
   466  
   467  // interpolationFuncElement implements the "element" function that allows
   468  // a specific index to be looked up in a multi-variable value. Note that this will
   469  // wrap if the index is larger than the number of elements in the multi-variable value.
   470  func interpolationFuncElement() ast.Function {
   471  	return ast.Function{
   472  		ArgTypes:   []ast.Type{ast.TypeString, ast.TypeString},
   473  		ReturnType: ast.TypeString,
   474  		Callback: func(args []interface{}) (interface{}, error) {
   475  			list := StringList(args[0].(string))
   476  
   477  			index, err := strconv.Atoi(args[1].(string))
   478  			if err != nil {
   479  				return "", fmt.Errorf(
   480  					"invalid number for index, got %s", args[1])
   481  			}
   482  
   483  			v := list.Element(index)
   484  			return v, nil
   485  		},
   486  	}
   487  }
   488  
   489  // interpolationFuncKeys implements the "keys" function that yields a list of
   490  // keys of map types within a Terraform configuration.
   491  func interpolationFuncKeys(vs map[string]ast.Variable) ast.Function {
   492  	return ast.Function{
   493  		ArgTypes:   []ast.Type{ast.TypeString},
   494  		ReturnType: ast.TypeString,
   495  		Callback: func(args []interface{}) (interface{}, error) {
   496  			// Prefix must include ending dot to be a map
   497  			prefix := fmt.Sprintf("var.%s.", args[0].(string))
   498  			keys := make([]string, 0, len(vs))
   499  			for k, _ := range vs {
   500  				if !strings.HasPrefix(k, prefix) {
   501  					continue
   502  				}
   503  				keys = append(keys, k[len(prefix):])
   504  			}
   505  
   506  			if len(keys) <= 0 {
   507  				return "", fmt.Errorf(
   508  					"failed to find map '%s'",
   509  					args[0].(string))
   510  			}
   511  
   512  			sort.Strings(keys)
   513  
   514  			return NewStringList(keys).String(), nil
   515  		},
   516  	}
   517  }
   518  
   519  // interpolationFuncValues implements the "values" function that yields a list of
   520  // keys of map types within a Terraform configuration.
   521  func interpolationFuncValues(vs map[string]ast.Variable) ast.Function {
   522  	return ast.Function{
   523  		ArgTypes:   []ast.Type{ast.TypeString},
   524  		ReturnType: ast.TypeString,
   525  		Callback: func(args []interface{}) (interface{}, error) {
   526  			// Prefix must include ending dot to be a map
   527  			prefix := fmt.Sprintf("var.%s.", args[0].(string))
   528  			keys := make([]string, 0, len(vs))
   529  			for k, _ := range vs {
   530  				if !strings.HasPrefix(k, prefix) {
   531  					continue
   532  				}
   533  				keys = append(keys, k)
   534  			}
   535  
   536  			if len(keys) <= 0 {
   537  				return "", fmt.Errorf(
   538  					"failed to find map '%s'",
   539  					args[0].(string))
   540  			}
   541  
   542  			sort.Strings(keys)
   543  
   544  			vals := make([]string, 0, len(keys))
   545  
   546  			for _, k := range keys {
   547  				v := vs[k]
   548  				if v.Type != ast.TypeString {
   549  					return "", fmt.Errorf("values(): %q has bad type %s", k, v.Type)
   550  				}
   551  				vals = append(vals, vs[k].Value.(string))
   552  			}
   553  
   554  			return NewStringList(vals).String(), nil
   555  		},
   556  	}
   557  }
   558  
   559  // interpolationFuncBase64Encode implements the "base64encode" function that
   560  // allows Base64 encoding.
   561  func interpolationFuncBase64Encode() ast.Function {
   562  	return ast.Function{
   563  		ArgTypes:   []ast.Type{ast.TypeString},
   564  		ReturnType: ast.TypeString,
   565  		Callback: func(args []interface{}) (interface{}, error) {
   566  			s := args[0].(string)
   567  			return base64.StdEncoding.EncodeToString([]byte(s)), nil
   568  		},
   569  	}
   570  }
   571  
   572  // interpolationFuncBase64Decode implements the "base64decode" function that
   573  // allows Base64 decoding.
   574  func interpolationFuncBase64Decode() ast.Function {
   575  	return ast.Function{
   576  		ArgTypes:   []ast.Type{ast.TypeString},
   577  		ReturnType: ast.TypeString,
   578  		Callback: func(args []interface{}) (interface{}, error) {
   579  			s := args[0].(string)
   580  			sDec, err := base64.StdEncoding.DecodeString(s)
   581  			if err != nil {
   582  				return "", fmt.Errorf("failed to decode base64 data '%s'", s)
   583  			}
   584  			return string(sDec), nil
   585  		},
   586  	}
   587  }
   588  
   589  // interpolationFuncLower implements the "lower" function that does
   590  // string lower casing.
   591  func interpolationFuncLower() ast.Function {
   592  	return ast.Function{
   593  		ArgTypes:   []ast.Type{ast.TypeString},
   594  		ReturnType: ast.TypeString,
   595  		Callback: func(args []interface{}) (interface{}, error) {
   596  			toLower := args[0].(string)
   597  			return strings.ToLower(toLower), nil
   598  		},
   599  	}
   600  }
   601  
   602  // interpolationFuncUpper implements the "upper" function that does
   603  // string upper casing.
   604  func interpolationFuncUpper() ast.Function {
   605  	return ast.Function{
   606  		ArgTypes:   []ast.Type{ast.TypeString},
   607  		ReturnType: ast.TypeString,
   608  		Callback: func(args []interface{}) (interface{}, error) {
   609  			toUpper := args[0].(string)
   610  			return strings.ToUpper(toUpper), nil
   611  		},
   612  	}
   613  }
   614  
   615  func interpolationFuncSha1() ast.Function {
   616  	return ast.Function{
   617  		ArgTypes:   []ast.Type{ast.TypeString},
   618  		ReturnType: ast.TypeString,
   619  		Callback: func(args []interface{}) (interface{}, error) {
   620  			s := args[0].(string)
   621  			h := sha1.New()
   622  			h.Write([]byte(s))
   623  			hash := hex.EncodeToString(h.Sum(nil))
   624  			return hash, nil
   625  		},
   626  	}
   627  }
   628  
   629  // hexadecimal representation of sha256 sum
   630  func interpolationFuncSha256() ast.Function {
   631  	return ast.Function{
   632  		ArgTypes:   []ast.Type{ast.TypeString},
   633  		ReturnType: ast.TypeString,
   634  		Callback: func(args []interface{}) (interface{}, error) {
   635  			s := args[0].(string)
   636  			h := sha256.New()
   637  			h.Write([]byte(s))
   638  			hash := hex.EncodeToString(h.Sum(nil))
   639  			return hash, nil
   640  		},
   641  	}
   642  }
   643  
   644  func interpolationFuncTrimSpace() ast.Function {
   645  	return ast.Function{
   646  		ArgTypes:   []ast.Type{ast.TypeString},
   647  		ReturnType: ast.TypeString,
   648  		Callback: func(args []interface{}) (interface{}, error) {
   649  			trimSpace := args[0].(string)
   650  			return strings.TrimSpace(trimSpace), nil
   651  		},
   652  	}
   653  }
   654  
   655  func interpolationFuncBase64Sha256() ast.Function {
   656  	return ast.Function{
   657  		ArgTypes:   []ast.Type{ast.TypeString},
   658  		ReturnType: ast.TypeString,
   659  		Callback: func(args []interface{}) (interface{}, error) {
   660  			s := args[0].(string)
   661  			h := sha256.New()
   662  			h.Write([]byte(s))
   663  			shaSum := h.Sum(nil)
   664  			encoded := base64.StdEncoding.EncodeToString(shaSum[:])
   665  			return encoded, nil
   666  		},
   667  	}
   668  }