Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,34 +1,18 @@
package org.jenkinsci.test.acceptance.docker.fixtures;

import java.io.IOException;
import java.net.URI;
import java.net.URL;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;
import org.gitlab4j.api.GitLabApi;
import org.gitlab4j.api.GitLabApiException;
import org.gitlab4j.api.ProjectApi;
import org.gitlab4j.api.models.Project;
import org.jenkinsci.test.acceptance.docker.Docker;
import org.jenkinsci.test.acceptance.docker.DockerContainer;
import org.jenkinsci.test.acceptance.docker.DockerFixture;
import org.jenkinsci.test.acceptance.po.CapybaraPortingLayer;
import org.jenkinsci.test.acceptance.utils.ElasticTime;
import org.jenkinsci.test.acceptance.po.CapybaraPortingLayerImpl;

@DockerFixture(
id = "gitlab-plugin",
ports = {80, 443, 22})
public class GitLabContainer extends DockerContainer {
protected static final String REPO_DIR = "/home/gitlab/gitlabRepo";

private static final HttpClient client = HttpClient.newBuilder()
.followRedirects(HttpClient.Redirect.NORMAL)
.connectTimeout(Duration.ofMillis(200))
.build();

private static final ElasticTime time = new ElasticTime();
private static final Duration READINESS_TIMEOUT = Duration.ofMinutes(5);
private static final Duration READINESS_POLL_INTERVAL = Duration.ofSeconds(5);

public String host() {
return ipBound(22);
Expand All @@ -46,67 +30,41 @@ public String httpHost() {
return ipBound(80);
}

public URL getURL() throws IOException {
return new URL("http://" + getIpAddress() + sshPort());
public String getHttpUrl() {
return "http://" + httpHost() + ":" + httpPort();
}

public URL getHttpUrl() throws IOException {
return new URL("http", httpHost(), httpPort(), "");
/**
* @return Authenticated Git URL ("http://username:token@host:port/username/repo.git")
*/
public String repoUrl(String projectPath, String token) {
String username = projectPath.split("/")[0];
return getHttpUrl().replace("://", "://" + username + ":" + token + "@") + "/" + projectPath + ".git";
}

/** URL visible from the host. */
public String getRepoUrl() {
return "ssh://git@" + host() + ":" + sshPort() + REPO_DIR;
/**
* Extracts the GitLab project path from an authenticated Git repository URL.
*
* @param repoUrl see {@link GitLabContainer#repoUrl(String, String)}
* @return Project path ("username/repo")
*/
public String extractProjectPath(String repoUrl) {
String afterAuth = repoUrl.split("@")[1];
return afterAuth.split("/", 2)[1].replace(".git", "");
}

public void waitForReady(CapybaraPortingLayer p) {
public void waitForReady(CapybaraPortingLayerImpl p) {
p.waitFor()
.withMessage("Waiting for GitLab to come up")
.withTimeout(Duration.ofSeconds(200)) // GitLab starts in about 2 minutes add some headway
.pollingEvery(Duration.ofSeconds(2))
.withTimeout(READINESS_TIMEOUT)
.pollingEvery(READINESS_POLL_INTERVAL)
.until(() -> {
try {
HttpRequest request = HttpRequest.newBuilder()
.uri(getHttpUrl().toURI())
.GET()
.timeout(Duration.ofSeconds(1))
.build();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
return response.body().contains("GitLab Community Edition");
} catch (IOException ignored) {
// we can not use .ignoring as this is a checked exception (even though a callable can throw
// this!)
return Boolean.FALSE;
}
p.executeScript("window.location.href = arguments[0];", getHttpUrl());
var page = p.getPageSource();
return page != null && page.contains("GitLab Community Edition");
});
}

public HttpResponse<String> createRepo(String repoName, String token) throws RuntimeException {
try {
HttpRequest request = HttpRequest.newBuilder()
.uri(new URI(getHttpUrl() + "/api/v4/projects"))
.header("Content-Type", "application/json")
.header("PRIVATE-TOKEN", token)
.POST(HttpRequest.BodyPublishers.ofString("{ \"name\": \"" + repoName + "\" }"))
.build();
return client.send(request, HttpResponse.BodyHandlers.ofString());
} catch (Exception e) {
throw new RuntimeException(e);
}
}

public void deleteRepo(String token, String repoName) throws IOException, GitLabApiException {
// get the project and delete the project
GitLabApi gitlabapi = new GitLabApi(getHttpUrl().toString(), token);
ProjectApi projApi = new ProjectApi(gitlabapi);

Project project = projApi.getProjects().stream()
.filter((proj -> repoName.equals(proj.getName())))
.findAny()
.orElse(null);
projApi.deleteProject(project);
}

public String createUserToken(String userName, String password, String email, String isAdmin)
throws IOException, InterruptedException {
return Docker.cmd("exec", getCid())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,7 @@ public void setOwner(String owner) {
find(by.path("/sources/source/projectOwner")).sendKeys(owner);
}

public void setProject(String owner, String project) {
find(by.path("/sources/source/projectPath")).click();
waitFor(by.option(owner + "/" + project)).click();
public void enableTagDiscovery() {
control("/hetero-list-add[traits]").selectDropdownMenu("Discover tags");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,7 @@ public GitLabOrganizationFolder(Injector injector, URL url, String name) {
}

public void create(String owner) {
control(by.path("/hetero-list-add[navigators]")).click();
find(by.partialLinkText("GitLab Group")).click();
control(by.path("/hetero-list-add[navigators]")).selectDropdownMenu("GitLab Group");
find(by.path("/navigators/projectOwner")).sendKeys(owner);
}

Expand All @@ -27,9 +26,10 @@ public String getCheckLog() {
return driver.getPageSource();
}

public GitLabOrganizationFolder waitForCheckFinished(final int timeout) {
public GitLabOrganizationFolder waitForCheckFinished(Duration timeout) {
waitFor()
.withTimeout(Duration.ofSeconds(timeout))
.withMessage("Waiting for GitLab group scan to finish in %s", this.name)
.withTimeout(timeout)
.until(() -> GitLabOrganizationFolder.this.getCheckLog().contains("Finished: "));

return this;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@

/**
* A pipeline multi-branch job (requires installation of multi-branch-project-plugin).
*
*/
@Describable("org.jenkinsci.plugins.workflow.multibranch.WorkflowMultiBranchProject")
public class WorkflowMultiBranchJob extends Folder {
Expand All @@ -31,15 +30,24 @@ public <T extends BranchSource> T addBranchSource(final Class<T> type) {
}

public String getBranchIndexingLog() {
try {
return IOUtils.toString(url("indexing/console").openStream(), StandardCharsets.UTF_8);
try (var in = url("indexing/console").openStream()) {
return IOUtils.toString(in, StandardCharsets.UTF_8);
} catch (IOException ex) {
throw new AssertionError(ex);
}
}

public String getBranchIndexingLogText() {
try (var in = url("indexing/consoleText").openStream()) {
return IOUtils.toString(in, StandardCharsets.UTF_8);
} catch (IOException ex) {
throw new AssertionError(ex);
}
}

public WorkflowMultiBranchJob waitForBranchIndexingFinished(final int timeout) {
waitFor()
.withMessage("Waiting for branch indexing to finish in %s", this.name)
.withTimeout(Duration.ofMillis(super.time.seconds(timeout)))
.until(() -> WorkflowMultiBranchJob.this.getBranchIndexingLog().contains("Finished: "));

Expand All @@ -50,6 +58,7 @@ public WorkflowJob getJob(final String name) {
return this.getJobs().get(WorkflowJob.class, name);
}

// NOTE: GitLab uses a different selector see GitLabPluginTest#reIndex
public void reIndex() {
final List<WebElement> scanRepoNow =
driver.findElements(by.xpath("//div[@class=\"task\"]//*[text()=\"Scan Repository Now\"]"));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@
FROM gitlab/gitlab-ce:18.6.1-ce.0

COPY create_user.rb /usr/bin/
COPY gitlab.rb /etc/gitlab/

ENV GITLAB_SKIP_PG_UPGRADE=true \
GITLAB_SKIP_TAIL_LOGS=true \
GITLAB_POST_RECONFIGURE_SCRIPT=""

# Expose the required ports
EXPOSE 80 443 22
Original file line number Diff line number Diff line change
@@ -1,15 +1,40 @@
input_array = ARGV

user = User.create();
user.name = input_array[0];
user.username = input_array[0];
user.password = input_array[1];
user.confirmed_at = '01/01/1990';
user.admin = input_array[3];
user.email = input_array[2];
user.save!;

token = user.personal_access_tokens.create(scopes: [:api], name: 'MyToken');
token.expires_at='01/01/2024';
token.save!;
puts token.token;
# frozen_string_literal: true

# Script to create GitLab user with personal access token
# Usage: gitlab-rails runner create_user.rb <username> <password> <email> <is_admin>

username, password, email, is_admin = ARGV

# Ensure default organization exists
default_org = Organizations::Organization.find_or_create_by!(name: 'Default', path: 'default') do |org|
org.description = 'Default organization for test users'
end

# Create user with namespace and organization
user_params = {
name: username,
username: username,
password: password,
password_confirmation: password,
email: email,
admin: is_admin == 'true',
skip_confirmation: true,
organization_id: default_org&.id
}.compact

result = Users::CreateService.new(nil, user_params).execute

raise "Failed to create user: #{result.message}" unless result.success?

user = result.payload[:user]

raise "Failed to create user. Result: #{result.inspect}" unless user&.persisted?

# Create personal access token with 1-month expiration
token = user.personal_access_tokens.create!(
scopes: [:api, :read_user, :read_api, :read_repository, :write_repository],
name: 'MyToken',
expires_at: 30.days.from_now
)

puts token.token
Loading