Skip to content

Commit

Permalink
Merge branch 'master' into 573-udpdate-http
Browse files Browse the repository at this point in the history
  • Loading branch information
olenagerasimova committed Sep 29, 2020
2 parents ddf1cdc + 5f51830 commit ef7fad2
Show file tree
Hide file tree
Showing 4 changed files with 182 additions and 54 deletions.
19 changes: 19 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,25 @@ meta:
interval: 5 # Publishing interval in seconds, default value is 5
```

## Single repository on port

Artipie repositories may run on separate ports if configured.
This feature may be especially useful for Docker repository,
as it's API is not well suited to serve multiple repositories on single port.
To run repository on its own port
`port` parameter should be specified in repository configuration YAML as follows:
```yaml
repo:
type: <repository type>
port: 54321
...
```
*NOTE: Artipie scans repositories for port configuration only on start,
so server requires restart in order to apply changes made in runtime.*
## Artipie REST API
Artipie provides a set of APIs to manage repositories and users. The current APIs are fully documented [here](./REST_API.md).
Expand Down
22 changes: 22 additions & 0 deletions examples/docker/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,28 @@ docker pull localhost:8080/my-docker/myfirstimage

#### Advanced options

##### Docker on port

We may also assign a port for the repository
to access the image by name without using `my-docker` prefix.
To do that we add `host: 8081` parameter to existing `my-docker.yaml`:

```yaml
repo:
host: 8081
type: docker
storage:
type: fs
path: /var/artipie/data
```

Now we may pull image `localhost:8080/my-docker/myfirstimage`
we pushed before as `localhost:8081/myfirstimage`:

```bash
docker pull localhost:8081/myfirstimage
```

##### Security

Docker registry has to be protected by HTTPS and should have no prefix in path.
Expand Down
10 changes: 5 additions & 5 deletions src/test/java/com/artipie/ArtipieServer.java
Original file line number Diff line number Diff line change
Expand Up @@ -57,18 +57,18 @@ public class ArtipieServer {
*/
public static final User CAROL = new User("carol", "LetMeIn");

/**
* Credentials file name.
*/
public static final String CREDENTIALS_FILE = "_credentials.yml";

/**
* All users.
*/
private static final Collection<User> USERS = Arrays.asList(
ArtipieServer.ALICE, ArtipieServer.BOB, ArtipieServer.CAROL
);

/**
* Credentials file name.
*/
private static final String CREDENTIALS_FILE = "_credentials.yml";

/**
* Root path.
*/
Expand Down
185 changes: 136 additions & 49 deletions src/test/java/com/artipie/FilesRepoITCase.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,24 +24,30 @@
package com.artipie;

import com.amihaiemil.eoyaml.Yaml;
import com.amihaiemil.eoyaml.YamlMapping;
import com.amihaiemil.eoyaml.YamlMappingBuilder;
import com.artipie.asto.Key;
import com.artipie.asto.Storage;
import com.artipie.asto.blocking.BlockingStorage;
import com.artipie.asto.fs.FileStorage;
import com.artipie.asto.test.TestResource;
import com.jcabi.log.Logger;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.regex.Pattern;
import org.hamcrest.MatcherAssert;
import org.hamcrest.core.IsEqual;
import org.hamcrest.core.StringContains;
import org.hamcrest.text.MatchesPattern;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.EnabledOnOs;
import org.junit.jupiter.api.condition.OS;
import org.junit.jupiter.api.io.TempDir;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import org.testcontainers.Testcontainers;
import org.testcontainers.containers.GenericContainer;

Expand All @@ -50,7 +56,10 @@
* @since 0.11
* @checkstyle ClassDataAbstractionCouplingCheck (500 lines)
*/
@SuppressWarnings("PMD.AvoidDuplicateLiterals")
@SuppressWarnings({
"PMD.AvoidDuplicateLiterals",
"PMD.TooManyMethods"
})
@EnabledOnOs({OS.LINUX, OS.MAC})
final class FilesRepoITCase {

Expand Down Expand Up @@ -81,29 +90,13 @@ final class FilesRepoITCase {
*/
private int port;

@BeforeEach
void init() throws Exception {
this.storage = new FileStorage(this.tmp);
this.server = new ArtipieServer(this.tmp, "my-file", this.config());
this.port = this.server.start();
this.server.start();
Testcontainers.exposeHostPorts(this.port);
this.cntn = new GenericContainer<>("centos:centos8")
.withCommand("tail", "-f", "/dev/null")
.withWorkingDirectory("/home/")
.withFileSystemBind(this.tmp.toString(), "/home");
this.cntn.start();
this.exec("yum", "-y", "install", "curl");
}

@Test
void curlGetShouldReceiveFile() throws Exception {
final String url = "http://host.testcontainers.internal:%d/my-file/file-repo/curl.txt";
this.addFilesToStorage(
"file-repo", new Key.From("repos", "my-file", "file-repo")
);
@ParameterizedTest
@ValueSource(booleans = {true, false})
void curlGetShouldReceiveFile(final boolean anonymous) throws Exception {
this.init(this.config(anonymous));
this.addFilesToStorage("file-repo", new Key.From("repos", "my-file", "file-repo"));
MatcherAssert.assertThat(
this.exec("curl", "-i", "-X", "GET", String.format(url, this.port)),
this.curl("GET", this.userOpt(anonymous)),
new MatchesPattern(
Pattern.compile(
// @checkstyle LineLengthCheck (1 line)
Expand All @@ -113,12 +106,13 @@ void curlGetShouldReceiveFile() throws Exception {
);
}

@Test
void curlPutShouldSaveFile() throws Exception {
final String url = "http://host.testcontainers.internal:%d/my-file/file-repo/curl.txt";
@ParameterizedTest
@ValueSource(booleans = {true, false})
void curlPutShouldSaveFile(final boolean anonymous) throws Exception {
this.init(this.config(anonymous));
MatcherAssert.assertThat(
"curl PUT does work properly",
this.exec("curl", "-i", "-X", "PUT", String.format(url, this.port)),
this.curl("PUT", this.userOpt(anonymous)),
new StringContains("HTTP/1.1 201 Created")
);
MatcherAssert.assertThat(
Expand All @@ -130,35 +124,118 @@ void curlPutShouldSaveFile() throws Exception {
);
}

@ParameterizedTest
@ValueSource(strings = {"PUT", "GET"})
void curlPutAndGetShouldFailWithUnauthorized(final String req) throws Exception {
this.init(this.config(false));
MatcherAssert.assertThat(
this.curl(
req, Optional.of(
new ArtipieServer.User(
ArtipieServer.ALICE.name(),
String.format("bad%s", ArtipieServer.ALICE.password())
)
)
),
new StringContains("HTTP/1.1 401 Unauthorized")
);
}

@ParameterizedTest
@ValueSource(strings = {"PUT", "GET"})
void curlPutAndGetShouldFailWithForbidden(final String req) throws Exception {
this.init(this.config(false));
MatcherAssert.assertThat(
this.curl(req, Optional.of(ArtipieServer.BOB)),
new StringContains("HTTP/1.1 403 Forbidden")
);
}

@AfterEach
void release() throws Exception {
void tearDown() {
this.server.stop();
this.cntn.stop();
}

private String exec(final String... command) throws Exception {
Logger.debug(this, "Command:\n%s", String.join(" ", command));
return this.cntn.execInContainer(command).getStdout();
private String curl(final String action,
final Optional<ArtipieServer.User> user) throws Exception {
final String url = "http://host.testcontainers.internal:%d/my-file/file-repo/curl.txt";
final List<String> cmdlst = new ArrayList<>(
Arrays.asList(
"curl", "-i", "-X", action, String.format(url, this.port)
)
);
user.ifPresent(
usr -> {
cmdlst.add("--user");
cmdlst.add(String.format("%s:%s", usr.name(), usr.password()));
}
);
final String[] cmdarr = cmdlst.toArray(new String[0]);
Logger.debug(this, "Command:\n%s", String.join(" ", cmdlst));
return this.cntn.execInContainer(cmdarr).getStdout();
}

private void init(final String config) throws Exception {
this.storage = new FileStorage(this.tmp);
this.server = new ArtipieServer(this.tmp, "my-file", config);
this.port = this.server.start();
this.server.start();
Testcontainers.exposeHostPorts(this.port);
this.cntn = new GenericContainer<>("centos:centos8")
.withCommand("tail", "-f", "/dev/null")
.withWorkingDirectory("/home/")
.withFileSystemBind(this.tmp.toString(), "/home");
this.cntn.start();
Logger.debug(this, "Command:\nyum -y install curl");
this.cntn.execInContainer("yum", "-y", "install", "curl");
}

private String config(final boolean anonymous) {
YamlMappingBuilder yaml = Yaml.createYamlMappingBuilder()
.add("type", "file")
.add(
"storage",
Yaml.createYamlMappingBuilder()
.add("type", "fs")
.add("path", this.tmp.resolve("repos").toString())
.build()
);
if (!anonymous) {
yaml = yaml.add(
"credentials",
Yaml.createYamlMappingBuilder()
.add("type", "file")
.add("path", ArtipieServer.CREDENTIALS_FILE)
.build()
)
.add(
"permissions",
this.perms()
);
}
return Yaml.createYamlMappingBuilder()
.add(
"repo", yaml.build()
).build().toString();
}

private String config() {
return Yaml.createYamlMappingBuilder().add(
"repo",
Yaml.createYamlMappingBuilder()
.add("type", "file")
.add(
"storage",
Yaml.createYamlMappingBuilder()
.add("type", "fs")
.add("path", this.tmp.resolve("repos").toString())
.build()
)
.build()
).build().toString();
private YamlMapping perms() {
return Yaml.createYamlMappingBuilder()
.add(
ArtipieServer.ALICE.name(),
Yaml.createYamlSequenceBuilder()
.add("write")
.add("download")
.build()
)
.add(
ArtipieServer.BOB.name(),
Yaml.createYamlSequenceBuilder().build()
).build();
}

private void addFilesToStorage(final String resource, final Key key)
throws InterruptedException {
private void addFilesToStorage(final String resource, final Key key) {
final Storage resources = new FileStorage(
new TestResource(resource).asPath()
);
Expand All @@ -170,4 +247,14 @@ private void addFilesToStorage(final String resource, final Key key)
);
}
}

private Optional<ArtipieServer.User> userOpt(final boolean anonymous) {
final Optional<ArtipieServer.User> user;
if (anonymous) {
user = Optional.empty();
} else {
user = Optional.of(ArtipieServer.ALICE);
}
return user;
}
}

0 comments on commit ef7fad2

Please sign in to comment.