Repository: pingcap/tidb (Go)

Issue:
br s3 endpoint can't end with '/'
## Bug Report

Please answer these questions before submitting your issue. Thanks!

This bug is a little bit frustrating. 

### 1. Minimal reproduce step (Required)

<!-- a step by step guide for reproducing the bug. -->
```
AWS_ACCESS_KEY_ID=xxxx AWS_SECRET_ACCESS_KEY=xxxxx tiup br backup full --s3.endpoint http://minio.pingcap.net:9000/ -s s3://tmp/sysbenc-empty-cy
```

### 2. What did you expect to see? (Required)
br backup successfully

### 3. What did you see instead (Required)
br backup failed
```
Error: error happen in store 1 at 127.0.0.1:20160: Io(Custom { kind: Other, error: "failed to put object Request ID: None Body: <?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Error><Code>BadRequest</Code><Message>An error occurred when parsing the HTTP request PUT at &#39;/
/tmp/sysbenc-empty-cy/1_2_28_2550d727ffe3e408af41f541b134d5e064440c25988bbecfb665933d1195a45d_1637743793014_write.sst&#39;</Message><Resource>//tmp/sysbenc-empty-cy/1_2_28_2550d727ffe3e408af41f541b134d5e064440c25988bbecfb665933d1195a45d_1637743793014_write.sst</Resource><
RequestId></RequestId><HostId>bf48f99f-3f89-4a20-bd11-95d1658fa9c8</HostId></Error>" }): [BR:KV:ErrKVStorage]tikv storage occur I/O error
Error: run `/root/.tiup/components/br/v5.3.0/br` (wd:/root/.tiup/data/Spfa3FJ) failed: exit status 1
```

### 4. What is your TiDB version? (Required)

<!-- Paste the output of SELECT tidb_version() -->

**5.3.0**


Discussion:
duplicated https://github.com/pingcap/br/issues/992
S3 endpoint w/o slash works:
![GJXNkBwlaJ](https://user-images.githubusercontent.com/85682690/143402213-d963a309-6106-4c68-86da-f2257a981eee.png)

minio endpoint w/o slash works:
![middle_img_v2_92e165a6-4a16-48b4-9434-43830ef78b3g](https://user-images.githubusercontent.com/85682690/143402373-3ed15b19-edad-472d-b33e-d340ae10a122.png)

According to definition of[ AWS endpoint](https://docs.aws.amazon.com/general/latest/gr/rande.html).

endpoint shall be a URL, and URL be w/o slash shall be valid.
Please check whether the issue should be labeled with 'affects-x.y' or 'fixes-x.y.z', and then remove 'needs-more-info' label.



Patch:
diff --git a/br/pkg/storage/s3.go b/br/pkg/storage/s3.go
index 6accafee7363d..f0a8a2a3a533e 100644
--- a/br/pkg/storage/s3.go
+++ b/br/pkg/storage/s3.go
@@ -155,7 +155,7 @@ func (options *S3BackendOptions) Apply(s3 *backuppb.S3) error {
 		return errors.Annotate(berrors.ErrStorageInvalidConfig, "secret_access_key not found")
 	}
 
-	s3.Endpoint = options.Endpoint
+	s3.Endpoint = strings.TrimSuffix(options.Endpoint, "/")
 	s3.Region = options.Region
 	// StorageClass, SSE and ACL are acceptable to be empty
 	s3.StorageClass = options.StorageClass
@@ -189,6 +189,7 @@ func (options *S3BackendOptions) parseFromFlags(flags *pflag.FlagSet) error {
 	if err != nil {
 		return errors.Trace(err)
 	}
+	options.Endpoint = strings.TrimSuffix(options.Endpoint, "/")
 	options.Region, err = flags.GetString(s3RegionOption)
 	if err != nil {
 		return errors.Trace(err)


Test patch:
diff --git a/br/pkg/storage/parse_test.go b/br/pkg/storage/parse_test.go
index fc3ee47a095b7..542b5dde3de07 100644
--- a/br/pkg/storage/parse_test.go
+++ b/br/pkg/storage/parse_test.go
@@ -57,7 +57,7 @@ func (r *testStorageSuite) TestCreateStorage(c *C) {
 	c.Assert(s3, NotNil)
 	c.Assert(s3.Bucket, Equals, "bucket2")
 	c.Assert(s3.Prefix, Equals, "prefix")
-	c.Assert(s3.Endpoint, Equals, "https://s3.example.com/")
+	c.Assert(s3.Endpoint, Equals, "https://s3.example.com")
 	c.Assert(s3.ForcePathStyle, IsFalse)
 
 	// nolint:lll