Skip to content

refactor(api): use ky and undici instead of got #756 #844

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ Create a [project access token](https://docs.gitlab.com/ee/user/project/settings

#### Proxy configuration

The plugin supports passing requests through a proxy server.
The plugin supports passing requests through a proxy server using `undici`'s `EnvHttpProxyAgent`.

You can configure a proxy server via the `HTTPS_PROXY` environment variable: `HTTPS_PROXY=http://proxyurl.com:8080`

Expand Down
12 changes: 6 additions & 6 deletions lib/fail.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { EnvHttpProxyAgent } from "undici";
import { template } from "lodash-es";
import urlJoin from "url-join";
import got from "got";
import ky from "ky";
import _debug from "debug";
const debug = _debug("semantic-release:gitlab");
import resolveConfig from "./resolve-config.js";
Expand Down Expand Up @@ -31,6 +32,7 @@ export default async (pluginConfig, context) => {
headers: { "PRIVATE-TOKEN": gitlabToken },
retry: { limit: retryLimit },
};
const kyInstance = ky.create(apiOptions).extend({ dispatcher: new EnvHttpProxyAgent() });

if (failComment === false || failTitle === false) {
logger.log("Skip issue creation.");
Expand All @@ -45,7 +47,7 @@ Using 'false' for 'failComment' or 'failTitle' is deprecated and will be removed
const issuesEndpoint = urlJoin(projectApiUrl, `issues`);
const openFailTitleIssueEndpoint = urlJoin(issuesEndpoint, `?state=opened&search=${encodedFailTitle}`);

const openFailTitleIssues = await got(openFailTitleIssueEndpoint, { ...apiOptions }).json();
const openFailTitleIssues = await kyInstance.get(openFailTitleIssueEndpoint).json();
const existingIssue = openFailTitleIssues.find((openFailTitleIssue) => openFailTitleIssue.title === failTitle);

const canCommentOnOrCreateIssue = failCommentCondition
Expand All @@ -59,8 +61,7 @@ Using 'false' for 'failComment' or 'failTitle' is deprecated and will be removed
gitlabApiUrl,
`/projects/${existingIssue.project_id}/issues/${existingIssue.iid}/notes`
);
await got.post(issueNotesEndpoint, {
...apiOptions,
await kyInstance.post(issueNotesEndpoint, {
json: { body: description },
});

Expand All @@ -71,9 +72,8 @@ Using 'false' for 'failComment' or 'failTitle' is deprecated and will be removed
debug("create issue: %O", newIssue);

/* eslint camelcase: off */
const { id, web_url } = await got
const { id, web_url } = await kyInstance
.post(issuesEndpoint, {
...apiOptions,
json: newIssue,
})
.json();
Expand Down
19 changes: 8 additions & 11 deletions lib/publish.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { EnvHttpProxyAgent } from "undici";
import { readFileSync } from "fs";
import pathlib from "path";
import fs from "fs-extra";
import { isPlainObject, template } from "lodash-es";
import { FormData } from "formdata-node";
import { fileFromPath } from "formdata-node/file-from-path";
import urlJoin from "url-join";
import got from "got";
import ky from "ky";
import _debug from "debug";
const debug = _debug("semantic-release:gitlab");
import resolveConfig from "./resolve-config.js";
Expand All @@ -22,10 +23,7 @@ export default async (pluginConfig, context) => {
nextRelease: { gitTag, gitHead, notes, version },
logger,
} = context;
const { gitlabToken, gitlabUrl, gitlabApiUrl, assets, milestones, proxy, retryLimit } = resolveConfig(
pluginConfig,
context
);
const { gitlabToken, gitlabUrl, gitlabApiUrl, assets, milestones, retryLimit } = resolveConfig(pluginConfig, context);
const assetsList = [];
const { projectPath, projectApiUrl } = getProjectContext(context, gitlabUrl, gitlabApiUrl, repositoryUrl);

Expand All @@ -42,7 +40,7 @@ export default async (pluginConfig, context) => {
if (response?.body && response.headers["content-type"] === "application/json") {
const parsedBody = JSON.parse(response.body);
if (parsedBody.message) {
error.message = `Response code ${response.statusCode} (${parsedBody.message})`;
error.message = `Response code ${response.status} (${parsedBody.message})`;
}
}
return error;
Expand All @@ -51,6 +49,7 @@ export default async (pluginConfig, context) => {
},
retry: { limit: retryLimit },
};
const kyInstance = ky.create(apiOptions).extend({ dispatcher: new EnvHttpProxyAgent() });

debug("projectPath: %o", projectPath);
debug("release name: %o", gitTag);
Expand Down Expand Up @@ -123,7 +122,7 @@ export default async (pluginConfig, context) => {
debug("PUT-ing the file %s to %s", file, uploadEndpoint);

try {
response = await got.put(uploadEndpoint, { ...apiOptions, ...proxy, body: readFileSync(file) }).json();
response = await kyInstance.put(uploadEndpoint, { body: readFileSync(file) }).json();
} catch (error) {
logger.error("An error occurred while uploading %s to the GitLab generics package API:\n%O", file, error);
throw error;
Expand All @@ -144,7 +143,7 @@ export default async (pluginConfig, context) => {
try {
const form = new FormData();
form.append("file", await fileFromPath(file));
response = await got.post(uploadEndpoint, { ...apiOptions, ...proxy, body: form }).json();
response = await kyInstance.post(uploadEndpoint, { body: form }).json();
} catch (error) {
logger.error("An error occurred while uploading %s to the GitLab project uploads API:\n%O", file, error);
throw error;
Expand Down Expand Up @@ -187,9 +186,7 @@ export default async (pluginConfig, context) => {
debug("POST-ing the following JSON to %s:\n%s", createReleaseEndpoint, JSON.stringify(json, null, 2));

try {
await got.post(createReleaseEndpoint, {
...apiOptions,
...proxy,
await kyInstance.post(createReleaseEndpoint, {
json,
});
} catch (error) {
Expand Down
103 changes: 0 additions & 103 deletions lib/resolve-config.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { castArray, isNil } from "lodash-es";
import urlJoin from "url-join";
import { HttpProxyAgent, HttpsProxyAgent } from "hpagent";

export default (
{
Expand Down Expand Up @@ -29,9 +28,6 @@ export default (
GITLAB_URL,
GL_PREFIX,
GITLAB_PREFIX,
HTTP_PROXY,
HTTPS_PROXY,
NO_PROXY,
},
}
) => {
Expand Down Expand Up @@ -60,7 +56,6 @@ export default (
milestones: milestones ? castArray(milestones) : milestones,
successComment,
successCommentCondition,
proxy: getProxyConfiguration(defaultedGitlabUrl, HTTP_PROXY, HTTPS_PROXY, NO_PROXY),
failTitle: isNil(failTitle) ? "The automated release is failing 🚨" : failTitle,
failComment,
failCommentCondition,
Expand All @@ -69,101 +64,3 @@ export default (
retryLimit: retryLimit ?? DEFAULT_RETRY_LIMIT,
};
};

// Copied from Rob Wu's great proxy-from-env library: https://github.com/Rob--W/proxy-from-env/blob/96d01f8fcfdccfb776735751132930bbf79c4a3a/index.js#L62
function shouldProxy(gitlabUrl, NO_PROXY) {
const DEFAULT_PORTS = {
ftp: 21,
gopher: 70,
http: 80,
https: 443,
ws: 80,
wss: 443,
};
const parsedUrl =
typeof gitlabUrl === "string" && (gitlabUrl.startsWith("http://") || gitlabUrl.startsWith("https://"))
? new URL(gitlabUrl)
: gitlabUrl || {};
let proto = parsedUrl.protocol;
let hostname = parsedUrl.host;
let { port } = parsedUrl;
if (typeof hostname !== "string" || !hostname || typeof proto !== "string") {
return ""; // Don't proxy URLs without a valid scheme or host.
}

proto = proto.split(":", 1)[0];
// Stripping ports in this way instead of using parsedUrl.hostname to make
// sure that the brackets around IPv6 addresses are kept.
hostname = hostname.replace(/:\d*$/, "");
port = Number.parseInt(port, 10) || DEFAULT_PORTS[proto] || 0;

if (!NO_PROXY) {
return true; // Always proxy if NO_PROXY is not set.
}

if (NO_PROXY === "*") {
return false; // Never proxy if wildcard is set.
}

return NO_PROXY.split(/[,\s]/).every((proxy) => {
if (!proxy) {
return true; // Skip zero-length hosts.
}

const parsedProxy = proxy.match(/^(.+):(\d+)$/);
let parsedProxyHostname = parsedProxy ? parsedProxy[1] : proxy;
const parsedProxyPort = parsedProxy ? Number.parseInt(parsedProxy[2], 10) : 0;
if (parsedProxyPort && parsedProxyPort !== port) {
return true; // Skip if ports don't match.
}

if (!/^[.*]/.test(parsedProxyHostname)) {
// No wildcards, so stop proxying if there is an exact match.
return hostname !== parsedProxyHostname;
}

if (parsedProxyHostname.charAt(0) === "*") {
// Remove leading wildcard.
parsedProxyHostname = parsedProxyHostname.slice(1);
}

// Stop proxying if the hostname ends with the no_proxy host.
return !hostname.endsWith(parsedProxyHostname);
});
}

function getProxyConfiguration(gitlabUrl, HTTP_PROXY, HTTPS_PROXY, NO_PROXY) {
const sharedParameters = {
keepAlive: true,
keepAliveMsecs: 1000,
maxSockets: 256,
maxFreeSockets: 256,
scheduling: "lifo",
};

if (shouldProxy(gitlabUrl, NO_PROXY)) {
if (HTTP_PROXY && gitlabUrl.startsWith("http://")) {
return {
agent: {
http: new HttpProxyAgent({
...sharedParameters,
proxy: HTTP_PROXY,
}),
},
};
}

if (HTTPS_PROXY && gitlabUrl.startsWith("https://")) {
return {
agent: {
https: new HttpsProxyAgent({
...sharedParameters,
proxy: HTTPS_PROXY,
}),
},
};
}
}

return {};
}
32 changes: 11 additions & 21 deletions lib/success.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { EnvHttpProxyAgent } from "undici";
import { uniqWith, isEqual, template } from "lodash-es";
import urlJoin from "url-join";
import got from "got";
import ky from "ky";
import _debug from "debug";
const debug = _debug("semantic-release:gitlab");
import resolveConfig from "./resolve-config.js";
Expand All @@ -15,13 +16,16 @@ export default async (pluginConfig, context) => {
commits,
releases,
} = context;
const { gitlabToken, gitlabUrl, gitlabApiUrl, successComment, successCommentCondition, proxy, retryLimit } =
resolveConfig(pluginConfig, context);
const { gitlabToken, gitlabUrl, gitlabApiUrl, successComment, successCommentCondition, retryLimit } = resolveConfig(
pluginConfig,
context
);
const { projectApiUrl } = getProjectContext(context, gitlabUrl, gitlabApiUrl, repositoryUrl);
const apiOptions = {
headers: { "PRIVATE-TOKEN": gitlabToken },
retry: { limit: retryLimit },
};
const kyInstance = ky.create(apiOptions).extend({ dispatcher: new EnvHttpProxyAgent() });

if (successComment === false) {
logger.log("Skip commenting on issues and pull requests.");
Expand All @@ -42,9 +46,7 @@ Using 'false' for 'successComment' is deprecated and will be removed in a future
const body = successComment
? template(successComment)({ ...context, issue, mergeRequest: false })
: getSuccessComment(issue, releaseInfos, nextRelease);
return got.post(issueNotesEndpoint, {
...apiOptions,
...proxy,
return kyInstance.post(issueNotesEndpoint, {
json: { body },
});
} else {
Expand All @@ -65,9 +67,7 @@ Using 'false' for 'successComment' is deprecated and will be removed in a future
const body = successComment
? template(successComment)({ ...context, issue: false, mergeRequest })
: getSuccessComment({ isMergeRequest: true, ...mergeRequest }, releaseInfos, nextRelease);
return got.post(mergeRequestNotesEndpoint, {
...apiOptions,
...proxy,
return kyInstance.post(mergeRequestNotesEndpoint, {
json: { body },
});
} else {
Expand All @@ -78,12 +78,7 @@ Using 'false' for 'successComment' is deprecated and will be removed in a future
const getRelatedMergeRequests = async (commitHash) => {
const relatedMergeRequestsEndpoint = urlJoin(projectApiUrl, `repository/commits/${commitHash}/merge_requests`);
debug("Getting MRs from %s", relatedMergeRequestsEndpoint);
const relatedMergeRequests = await got
.get(relatedMergeRequestsEndpoint, {
...apiOptions,
...proxy,
})
.json();
const relatedMergeRequests = await kyInstance.get(relatedMergeRequestsEndpoint).json();

return relatedMergeRequests.filter((x) => x.state === "merged");
};
Expand All @@ -94,12 +89,7 @@ Using 'false' for 'successComment' is deprecated and will be removed in a future
`/projects/${mergeRequest.project_id}/merge_requests/${mergeRequest.iid}/closes_issues`
);
debug("Getting related issues from %s", relatedIssuesEndpoint);
const relatedIssues = await got
.get(relatedIssuesEndpoint, {
...apiOptions,
...proxy,
})
.json();
const relatedIssues = await kyInstance.get(relatedIssuesEndpoint).json();

return relatedIssues.filter((x) => x.state === "closed");
};
Expand Down
13 changes: 7 additions & 6 deletions lib/verify.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { EnvHttpProxyAgent } from "undici";
import { isString, isPlainObject, isNil, isArray } from "lodash-es";
import got from "got";
import ky from "ky";
import _debug from "debug";
const debug = _debug("semantic-release:gitlab");
import AggregateError from "aggregate-error";
Expand Down Expand Up @@ -30,7 +31,7 @@ export default async (pluginConfig, context) => {
options: { repositoryUrl },
logger,
} = context;
const { gitlabToken, gitlabUrl, gitlabApiUrl, proxy, ...options } = resolveConfig(pluginConfig, context);
const { gitlabToken, gitlabUrl, gitlabApiUrl, ...options } = resolveConfig(pluginConfig, context);
const { projectPath, projectApiUrl } = getProjectContext(context, gitlabUrl, gitlabApiUrl, repositoryUrl);

debug("apiUrl: %o", gitlabApiUrl);
Expand Down Expand Up @@ -62,10 +63,10 @@ export default async (pluginConfig, context) => {
try {
({
permissions: { project_access: projectAccess, group_access: groupAccess },
} = await got
} = await ky
.extend({ dispatcher: new EnvHttpProxyAgent() })
.get(projectApiUrl, {
headers: { "PRIVATE-TOKEN": gitlabToken },
...proxy,
})
.json());
if (
Expand All @@ -79,9 +80,9 @@ export default async (pluginConfig, context) => {
errors.push(getError("EGLNOPUSHPERMISSION", { projectPath }));
}
} catch (error) {
if (error.response && error.response.statusCode === 401) {
if (error.response && error.response.status === 401) {
errors.push(getError("EINVALIDGLTOKEN", { projectPath }));
} else if (error.response && error.response.statusCode === 404) {
} else if (error.response && error.response.status === 404) {
errors.push(getError("EMISSINGREPO", { projectPath }));
} else {
throw error;
Expand Down
Loading
Loading