Repository: iterative/dvc
Issue: Unable to dvc add more than 1 file at a time in s3 bucket

** Note: DVC version 0.66.1, pip install, Ubuntu 16.04

I am having difficulty configuring DVC to track files on an s3 bucket with the structure

```
ellesdatabucket
       ├── data
       │     ├── img1.png
       │     ├── img2.png
       │     ├── ...
       └── cache
```

Specifically, I want to use DVC to version control *.png files stored in the data folder, and use the cache folder as DVC's cache.

Based on the docs provided [here](https://dvc.org/doc/user-guide/managing-external-data#amazon-s-3), I believe I've replicated exactly the provided steps. But I hit an error when I run `dvc add`:
```
$ git init
$ dvc init
$ dvc remote add myremote s3://ellesdatabucket/data
$ dvc remote add s3cache s3://ellesdatabucket/cache
$ dvc config cache.s3 s3cache
$ dvc add s3://ellesdatabucket/data
```
The output looks initially encouraging,
``` 0%|          |Add                                      0/1 [00:00<?,     ?file/s]
 29%|██▊       |Computing hashes (only done once691/2424 [00:03<00:07,    223md5/s]
```

But then I get this error message:

```100%|██████████|Add                                  1/1 [00:07<00:00,  7.56s/file]
ERROR: s3://ellesdatabucket/data/. does not exist: An error occurred (404) when calling the HeadObject operation: Not Found
```

I'm positive that there are files in the data folder and can view them by `aws s3 ls s3://ellesdatabucket/data`. And, if I try to run `dvc add` with only a single file at a time instead of a whole directory, the command completes successfully. Although a bash script could `dvc add` each file in a loop, I want to make sure there's not a better way. [Issue 2647](https://github.com/iterative/dvc/issues/2647) seems to be discussing a similar problem but I can't figure out how to apply that code to my own example here. 

Thank you for any help!

**More context**:

https://discordapp.com/channels/485586884165107732/485596304961962003/637836708184064010



Changed files:
tests/unit/remote/test_s3.py, dvc/remote/s3.py

diff --git a/dvc/remote/s3.py b/dvc/remote/s3.py
index 5594806f0..8013f9bb9 100644
--- a/dvc/remote/s3.py
+++ b/dvc/remote/s3.py
@@ -212,6 +212,15 @@ class RemoteS3(RemoteBASE):
         fname = next(self._list_paths(path_info, max_items=1), "")
         return path_info.path == fname or fname.startswith(dir_path.path)
 
+    def makedirs(self, path_info):
+        # We need to support creating empty directories, which means
+        # creating an object with an empty body and a trailing slash `/`.
+        #
+        # We are not creating directory objects for every parent prefix,
+        # as it is not required.
+        dir_path = path_info / ""
+        self.s3.put_object(Bucket=path_info.bucket, Key=dir_path.path, Body="")
+
     def isdir(self, path_info):
         # S3 doesn't have a concept for directories.
         #
@@ -271,4 +280,7 @@ class RemoteS3(RemoteBASE):
 
     def walk_files(self, path_info, max_items=None):
         for fname in self._list_paths(path_info, max_items):
+            if fname.endswith("/"):
+                continue
+
             yield path_info.replace(path=fname)
diff --git a/tests/unit/remote/test_s3.py b/tests/unit/remote/test_s3.py
index a82d005c4..49fc3dbb8 100644
--- a/tests/unit/remote/test_s3.py
+++ b/tests/unit/remote/test_s3.py
@@ -79,9 +79,11 @@ def test_walk_files(remote):
         remote.path_info / "data/subdir/1",
         remote.path_info / "data/subdir/2",
         remote.path_info / "data/subdir/3",
+        remote.path_info / "empty_file",
+        remote.path_info / "foo",
     ]
 
-    assert list(remote.walk_files(remote.path_info / "data")) == files
+    assert list(remote.walk_files(remote.path_info)) == files
 
 
 def test_copy_preserve_etag_across_buckets(remote):
@@ -99,3 +101,11 @@ def test_copy_preserve_etag_across_buckets(remote):
     to_etag = RemoteS3.get_etag(s3, "another", "foo")
 
     assert from_etag == to_etag
+
+
+def test_makedirs(remote):
+    empty_dir = remote.path_info / "empty_dir" / ""
+    remote.remove(empty_dir)
+    assert not remote.exists(empty_dir)
+    remote.makedirs(empty_dir)
+    assert remote.exists(empty_dir)