2013-07-02 22:11:30 -04:00
|
|
|
package file
|
|
|
|
|
|
|
|
import (
|
2018-09-06 09:52:28 -04:00
|
|
|
"bytes"
|
2019-03-19 13:11:19 -04:00
|
|
|
"context"
|
2013-07-02 22:11:30 -04:00
|
|
|
"io/ioutil"
|
|
|
|
"os"
|
2016-07-06 16:42:26 -04:00
|
|
|
"path/filepath"
|
2020-08-07 02:38:30 -04:00
|
|
|
"regexp"
|
2013-07-02 22:11:30 -04:00
|
|
|
"strings"
|
|
|
|
"testing"
|
2016-07-06 16:42:26 -04:00
|
|
|
|
2020-12-17 16:29:25 -05:00
|
|
|
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
2013-07-02 22:11:30 -04:00
|
|
|
)
|
|
|
|
|
2013-07-04 15:50:00 -04:00
|
|
|
func testConfig() map[string]interface{} {
|
|
|
|
return map[string]interface{}{
|
|
|
|
"destination": "something",
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-07-02 22:11:30 -04:00
|
|
|
func TestProvisioner_Impl(t *testing.T) {
|
|
|
|
var raw interface{}
|
|
|
|
raw = &Provisioner{}
|
2020-12-01 17:25:14 -05:00
|
|
|
if _, ok := raw.(packersdk.Provisioner); !ok {
|
2013-07-02 22:11:30 -04:00
|
|
|
t.Fatalf("must be a provisioner")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-07-13 20:28:56 -04:00
|
|
|
func TestProvisionerPrepare_InvalidKey(t *testing.T) {
|
|
|
|
var p Provisioner
|
|
|
|
config := testConfig()
|
|
|
|
|
|
|
|
// Add a random key
|
|
|
|
config["i_should_not_be_valid"] = true
|
|
|
|
err := p.Prepare(config)
|
|
|
|
if err == nil {
|
|
|
|
t.Fatal("should have error")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-07-02 22:11:30 -04:00
|
|
|
func TestProvisionerPrepare_InvalidSource(t *testing.T) {
|
|
|
|
var p Provisioner
|
2013-07-04 15:50:00 -04:00
|
|
|
config := testConfig()
|
|
|
|
config["source"] = "/this/should/not/exist"
|
2013-07-02 22:11:30 -04:00
|
|
|
|
|
|
|
err := p.Prepare(config)
|
|
|
|
if err == nil {
|
|
|
|
t.Fatalf("should require existing file")
|
|
|
|
}
|
2016-09-15 16:15:56 -04:00
|
|
|
|
|
|
|
config["generated"] = false
|
|
|
|
err = p.Prepare(config)
|
|
|
|
if err == nil {
|
|
|
|
t.Fatalf("should required existing file")
|
|
|
|
}
|
2013-07-02 22:11:30 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
func TestProvisionerPrepare_ValidSource(t *testing.T) {
|
|
|
|
var p Provisioner
|
|
|
|
|
|
|
|
tf, err := ioutil.TempFile("", "packer")
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("error tempfile: %s", err)
|
|
|
|
}
|
|
|
|
defer os.Remove(tf.Name())
|
|
|
|
|
2013-07-04 15:50:00 -04:00
|
|
|
config := testConfig()
|
|
|
|
config["source"] = tf.Name()
|
2016-09-15 16:15:56 -04:00
|
|
|
err = p.Prepare(config)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("should allow valid file: %s", err)
|
|
|
|
}
|
2013-07-02 22:11:30 -04:00
|
|
|
|
2016-09-15 16:15:56 -04:00
|
|
|
config["generated"] = false
|
2013-07-04 15:50:00 -04:00
|
|
|
err = p.Prepare(config)
|
2013-07-02 22:11:30 -04:00
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("should allow valid file: %s", err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-09-15 16:15:56 -04:00
|
|
|
func TestProvisionerPrepare_GeneratedSource(t *testing.T) {
|
|
|
|
var p Provisioner
|
|
|
|
|
|
|
|
config := testConfig()
|
|
|
|
config["source"] = "/this/should/not/exist"
|
|
|
|
config["generated"] = true
|
|
|
|
err := p.Prepare(config)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("should allow non-existing file: %s", err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-07-02 22:11:30 -04:00
|
|
|
func TestProvisionerPrepare_EmptyDestination(t *testing.T) {
|
|
|
|
var p Provisioner
|
|
|
|
|
2013-07-04 15:50:00 -04:00
|
|
|
config := testConfig()
|
|
|
|
delete(config, "destination")
|
2013-07-02 22:11:30 -04:00
|
|
|
err := p.Prepare(config)
|
|
|
|
if err == nil {
|
|
|
|
t.Fatalf("should require destination path")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestProvisionerProvision_SendsFile(t *testing.T) {
|
|
|
|
var p Provisioner
|
|
|
|
tf, err := ioutil.TempFile("", "packer")
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("error tempfile: %s", err)
|
|
|
|
}
|
|
|
|
defer os.Remove(tf.Name())
|
2013-07-04 15:50:00 -04:00
|
|
|
|
2013-07-02 22:11:30 -04:00
|
|
|
if _, err = tf.Write([]byte("hello")); err != nil {
|
|
|
|
t.Fatalf("error writing tempfile: %s", err)
|
|
|
|
}
|
2013-07-04 15:50:00 -04:00
|
|
|
|
|
|
|
config := map[string]interface{}{
|
2013-07-05 14:00:18 -04:00
|
|
|
"source": tf.Name(),
|
2013-07-04 15:50:00 -04:00
|
|
|
"destination": "something",
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := p.Prepare(config); err != nil {
|
|
|
|
t.Fatalf("err: %s", err)
|
|
|
|
}
|
2013-07-02 22:11:30 -04:00
|
|
|
|
2018-09-06 09:52:28 -04:00
|
|
|
b := bytes.NewBuffer(nil)
|
2020-11-20 13:21:29 -05:00
|
|
|
ui := &packersdk.BasicUi{
|
2018-09-06 09:52:28 -04:00
|
|
|
Writer: b,
|
2020-12-03 17:58:07 -05:00
|
|
|
PB: &packersdk.NoopProgressTracker{},
|
2018-09-06 09:52:28 -04:00
|
|
|
}
|
2020-11-20 13:21:29 -05:00
|
|
|
comm := &packersdk.MockCommunicator{}
|
2019-12-12 13:59:44 -05:00
|
|
|
err = p.Provision(context.Background(), ui, comm, make(map[string]interface{}))
|
2013-07-02 22:11:30 -04:00
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("should successfully provision: %s", err)
|
|
|
|
}
|
2013-07-04 15:50:00 -04:00
|
|
|
|
2018-09-06 09:52:28 -04:00
|
|
|
if !strings.Contains(b.String(), tf.Name()) {
|
2013-07-02 22:11:30 -04:00
|
|
|
t.Fatalf("should print source filename")
|
|
|
|
}
|
2013-07-04 15:50:00 -04:00
|
|
|
|
2018-09-06 09:52:28 -04:00
|
|
|
if !strings.Contains(b.String(), "something") {
|
2013-07-02 22:11:30 -04:00
|
|
|
t.Fatalf("should print destination filename")
|
|
|
|
}
|
2013-07-04 15:50:00 -04:00
|
|
|
|
2013-08-25 23:29:50 -04:00
|
|
|
if comm.UploadPath != "something" {
|
2013-07-02 22:11:30 -04:00
|
|
|
t.Fatalf("should upload to configured destination")
|
|
|
|
}
|
2013-07-04 15:50:00 -04:00
|
|
|
|
2013-08-25 23:29:50 -04:00
|
|
|
if comm.UploadData != "hello" {
|
2013-07-02 22:11:30 -04:00
|
|
|
t.Fatalf("should upload with source file's data")
|
|
|
|
}
|
|
|
|
}
|
2016-07-06 16:42:26 -04:00
|
|
|
|
2020-08-06 14:04:00 -04:00
|
|
|
func TestProvisionerProvision_SendsFileMultipleFiles(t *testing.T) {
|
|
|
|
var p Provisioner
|
|
|
|
tf1, err := ioutil.TempFile("", "packer")
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("error tempfile: %s", err)
|
|
|
|
}
|
|
|
|
defer os.Remove(tf1.Name())
|
|
|
|
|
|
|
|
if _, err = tf1.Write([]byte("hello")); err != nil {
|
|
|
|
t.Fatalf("error writing tempfile: %s", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
tf2, err := ioutil.TempFile("", "packer")
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("error tempfile: %s", err)
|
|
|
|
}
|
|
|
|
defer os.Remove(tf2.Name())
|
|
|
|
|
|
|
|
if _, err = tf2.Write([]byte("hello")); err != nil {
|
|
|
|
t.Fatalf("error writing tempfile: %s", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
config := map[string]interface{}{
|
2020-08-06 14:14:53 -04:00
|
|
|
"sources": []string{tf1.Name(), tf2.Name()},
|
2020-08-06 14:04:00 -04:00
|
|
|
"destination": "something",
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := p.Prepare(config); err != nil {
|
|
|
|
t.Fatalf("err: %s", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
b := bytes.NewBuffer(nil)
|
2020-11-20 13:21:29 -05:00
|
|
|
ui := &packersdk.BasicUi{
|
2020-08-06 14:04:00 -04:00
|
|
|
Writer: b,
|
2020-12-03 17:58:07 -05:00
|
|
|
PB: &packersdk.NoopProgressTracker{},
|
2020-08-06 14:04:00 -04:00
|
|
|
}
|
2020-11-20 13:21:29 -05:00
|
|
|
comm := &packersdk.MockCommunicator{}
|
2020-08-06 14:04:00 -04:00
|
|
|
err = p.Provision(context.Background(), ui, comm, make(map[string]interface{}))
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("should successfully provision: %s", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if !strings.Contains(b.String(), tf1.Name()) {
|
|
|
|
t.Fatalf("should print first source filename")
|
|
|
|
}
|
|
|
|
|
|
|
|
if !strings.Contains(b.String(), tf2.Name()) {
|
|
|
|
t.Fatalf("should print second source filename")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestProvisionerProvision_SendsFileMultipleDirs(t *testing.T) {
|
|
|
|
var p Provisioner
|
|
|
|
|
|
|
|
// Prepare the first directory
|
|
|
|
td1, err := ioutil.TempDir("", "packerdir")
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("error temp folder 1: %s", err)
|
|
|
|
}
|
|
|
|
defer os.Remove(td1)
|
|
|
|
|
|
|
|
tf1, err := ioutil.TempFile(td1, "packer")
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("error tempfile: %s", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if _, err = tf1.Write([]byte("hello")); err != nil {
|
|
|
|
t.Fatalf("error writing tempfile: %s", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Prepare the second directory
|
|
|
|
td2, err := ioutil.TempDir("", "packerdir")
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("error temp folder 1: %s", err)
|
|
|
|
}
|
|
|
|
defer os.Remove(td2)
|
|
|
|
|
|
|
|
tf2, err := ioutil.TempFile(td2, "packer")
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("error tempfile: %s", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if _, err = tf2.Write([]byte("hello")); err != nil {
|
|
|
|
t.Fatalf("error writing tempfile: %s", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if _, err = tf1.Write([]byte("hello")); err != nil {
|
|
|
|
t.Fatalf("error writing tempfile: %s", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Run Provision
|
|
|
|
|
|
|
|
config := map[string]interface{}{
|
2020-08-06 14:14:53 -04:00
|
|
|
"sources": []string{td1, td2},
|
2020-08-06 14:04:00 -04:00
|
|
|
"destination": "something",
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := p.Prepare(config); err != nil {
|
|
|
|
t.Fatalf("err: %s", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
b := bytes.NewBuffer(nil)
|
2020-11-20 13:21:29 -05:00
|
|
|
ui := &packersdk.BasicUi{
|
2020-08-06 14:04:00 -04:00
|
|
|
Writer: b,
|
2020-12-03 17:58:07 -05:00
|
|
|
PB: &packersdk.NoopProgressTracker{},
|
2020-08-06 14:04:00 -04:00
|
|
|
}
|
2020-11-20 13:21:29 -05:00
|
|
|
comm := &packersdk.MockCommunicator{}
|
2020-08-06 14:04:00 -04:00
|
|
|
err = p.Provision(context.Background(), ui, comm, make(map[string]interface{}))
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("should successfully provision: %s", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if !strings.Contains(b.String(), td1) {
|
|
|
|
t.Fatalf("should print first directory")
|
|
|
|
}
|
|
|
|
|
|
|
|
if !strings.Contains(b.String(), td2) {
|
|
|
|
t.Fatalf("should print second directory")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-03-05 12:06:47 -05:00
|
|
|
func TestProvisionerProvision_DownloadsMultipleFilesToFolder(t *testing.T) {
|
|
|
|
var p Provisioner
|
|
|
|
|
|
|
|
tf1, err := ioutil.TempFile("", "packer")
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("error tempfile: %s", err)
|
|
|
|
}
|
|
|
|
defer os.Remove(tf1.Name())
|
|
|
|
|
|
|
|
if _, err = tf1.Write([]byte("hello")); err != nil {
|
|
|
|
t.Fatalf("error writing tempfile: %s", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
tf2, err := ioutil.TempFile("", "packer")
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("error tempfile: %s", err)
|
|
|
|
}
|
|
|
|
defer os.Remove(tf2.Name())
|
|
|
|
|
|
|
|
if _, err = tf2.Write([]byte("hello")); err != nil {
|
|
|
|
t.Fatalf("error writing tempfile: %s", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
config := map[string]interface{}{
|
|
|
|
"sources": []string{tf1.Name(), tf2.Name()},
|
|
|
|
"destination": "something/",
|
|
|
|
"direction": "download",
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := p.Prepare(config); err != nil {
|
|
|
|
t.Fatalf("err: %s", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
b := bytes.NewBuffer(nil)
|
|
|
|
ui := &packersdk.BasicUi{
|
|
|
|
Writer: b,
|
|
|
|
PB: &packersdk.NoopProgressTracker{},
|
|
|
|
}
|
|
|
|
comm := &packersdk.MockCommunicator{}
|
|
|
|
err = p.Provision(context.Background(), ui, comm, make(map[string]interface{}))
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("should successfully provision: %s", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if !strings.Contains(b.String(), tf1.Name()) {
|
|
|
|
t.Errorf("should print source filenam '%s'e; output: \n%s", tf1.Name(), b.String())
|
|
|
|
}
|
|
|
|
|
|
|
|
if !strings.Contains(b.String(), tf2.Name()) {
|
|
|
|
t.Errorf("should second source filename '%s'; output: \n%s", tf2.Name(), b.String())
|
|
|
|
}
|
|
|
|
|
|
|
|
dst1 := filepath.Join("something", filepath.Base(tf1.Name()))
|
|
|
|
if !strings.Contains(b.String(), dst1) {
|
|
|
|
t.Errorf("should print destination filename '%s'; output: \n%s", dst1, b.String())
|
|
|
|
}
|
|
|
|
|
|
|
|
dst2 := filepath.Join("something", filepath.Base(tf2.Name()))
|
|
|
|
if !strings.Contains(b.String(), dst2) {
|
|
|
|
t.Errorf("should print destination filename '%s'; output: \n%s", dst2, b.String())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-08-07 02:38:30 -04:00
|
|
|
func TestProvisionerProvision_SendsFileMultipleFilesToFolder(t *testing.T) {
|
|
|
|
var p Provisioner
|
|
|
|
|
|
|
|
tf1, err := ioutil.TempFile("", "packer")
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("error tempfile: %s", err)
|
|
|
|
}
|
|
|
|
defer os.Remove(tf1.Name())
|
|
|
|
|
|
|
|
if _, err = tf1.Write([]byte("hello")); err != nil {
|
|
|
|
t.Fatalf("error writing tempfile: %s", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
tf2, err := ioutil.TempFile("", "packer")
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("error tempfile: %s", err)
|
|
|
|
}
|
|
|
|
defer os.Remove(tf2.Name())
|
|
|
|
|
|
|
|
if _, err = tf2.Write([]byte("hello")); err != nil {
|
|
|
|
t.Fatalf("error writing tempfile: %s", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
config := map[string]interface{}{
|
|
|
|
"sources": []string{tf1.Name(), tf2.Name()},
|
|
|
|
"destination": "something/",
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := p.Prepare(config); err != nil {
|
|
|
|
t.Fatalf("err: %s", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
b := bytes.NewBuffer(nil)
|
2020-11-20 13:21:29 -05:00
|
|
|
ui := &packersdk.BasicUi{
|
2020-08-07 02:38:30 -04:00
|
|
|
Writer: b,
|
2020-12-03 17:58:07 -05:00
|
|
|
PB: &packersdk.NoopProgressTracker{},
|
2020-08-07 02:38:30 -04:00
|
|
|
}
|
2020-11-20 13:21:29 -05:00
|
|
|
comm := &packersdk.MockCommunicator{}
|
2020-08-07 02:38:30 -04:00
|
|
|
err = p.Provision(context.Background(), ui, comm, make(map[string]interface{}))
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("should successfully provision: %s", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if !strings.Contains(b.String(), tf1.Name()) {
|
|
|
|
t.Fatalf("should print first source filename")
|
|
|
|
}
|
|
|
|
|
|
|
|
if !strings.Contains(b.String(), tf2.Name()) {
|
|
|
|
t.Fatalf("should print second source filename")
|
|
|
|
}
|
|
|
|
|
|
|
|
dstRegex := regexp.MustCompile("something/\n")
|
|
|
|
allDst := dstRegex.FindAllString(b.String(), -1)
|
|
|
|
if len(allDst) != 2 {
|
|
|
|
t.Fatalf("some destinations are broken; output: \n%s", b.String())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-07-06 16:42:26 -04:00
|
|
|
func TestProvisionDownloadMkdirAll(t *testing.T) {
|
|
|
|
tests := []struct {
|
|
|
|
path string
|
|
|
|
}{
|
|
|
|
{"dir"},
|
|
|
|
{"dir/"},
|
|
|
|
{"dir/subdir"},
|
|
|
|
{"dir/subdir/"},
|
|
|
|
{"path/to/dir"},
|
|
|
|
{"path/to/dir/"},
|
|
|
|
}
|
|
|
|
tmpDir, err := ioutil.TempDir("", "packer-file")
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("error tempdir: %s", err)
|
|
|
|
}
|
|
|
|
defer os.RemoveAll(tmpDir)
|
|
|
|
tf, err := ioutil.TempFile(tmpDir, "packer")
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("error tempfile: %s", err)
|
|
|
|
}
|
|
|
|
defer os.Remove(tf.Name())
|
|
|
|
|
|
|
|
config := map[string]interface{}{
|
|
|
|
"source": tf.Name(),
|
|
|
|
}
|
|
|
|
var p Provisioner
|
|
|
|
for _, test := range tests {
|
|
|
|
path := filepath.Join(tmpDir, test.path)
|
|
|
|
config["destination"] = filepath.Join(path, "something")
|
|
|
|
if err := p.Prepare(config); err != nil {
|
|
|
|
t.Fatalf("err: %s", err)
|
|
|
|
}
|
2018-09-06 09:52:28 -04:00
|
|
|
b := bytes.NewBuffer(nil)
|
2020-11-20 13:21:29 -05:00
|
|
|
ui := &packersdk.BasicUi{
|
2018-09-06 09:52:28 -04:00
|
|
|
Writer: b,
|
2020-12-03 17:58:07 -05:00
|
|
|
PB: &packersdk.NoopProgressTracker{},
|
2018-09-06 09:52:28 -04:00
|
|
|
}
|
2020-11-20 13:21:29 -05:00
|
|
|
comm := &packersdk.MockCommunicator{}
|
2016-07-06 16:42:26 -04:00
|
|
|
err = p.ProvisionDownload(ui, comm)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("should successfully provision: %s", err)
|
|
|
|
}
|
|
|
|
|
2018-09-06 09:52:28 -04:00
|
|
|
if !strings.Contains(b.String(), tf.Name()) {
|
2016-07-06 16:42:26 -04:00
|
|
|
t.Fatalf("should print source filename")
|
|
|
|
}
|
|
|
|
|
2018-09-06 09:52:28 -04:00
|
|
|
if !strings.Contains(b.String(), "something") {
|
2016-07-06 16:42:26 -04:00
|
|
|
t.Fatalf("should print destination filename")
|
|
|
|
}
|
|
|
|
|
|
|
|
if _, err := os.Stat(path); err != nil {
|
|
|
|
t.Fatalf("stat of download dir should not error: %s", err)
|
|
|
|
}
|
2016-09-18 22:36:18 -04:00
|
|
|
|
|
|
|
if _, err := os.Stat(config["destination"].(string)); err != nil {
|
|
|
|
t.Fatalf("stat of destination file should not error: %s", err)
|
|
|
|
}
|
2016-07-06 16:42:26 -04:00
|
|
|
}
|
|
|
|
}
|