-
Notifications
You must be signed in to change notification settings - Fork 36
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
feat(jfrog): support multiple repositories #289
Changes from 7 commits
31c29fa
646adbc
0361847
b6c0d58
fd5ba0f
67c1d63
4b0ac2a
d06cd4a
149cb2f
70ec87f
fb409cf
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
email=${ARTIFACTORY_EMAIL} | ||
%{ for REPO in REPOS ~} | ||
${REPO.SCOPE}registry=${JFROG_URL}/artifactory/api/npm/${REPO.NAME} | ||
//${JFROG_HOST}/artifactory/api/npm/${REPO.NAME}/:_authToken=${ARTIFACTORY_ACCESS_TOKEN} | ||
%{ endfor ~} |
Original file line number | Diff line number | Diff line change | ||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -1,19 +1,115 @@ | ||||||||||||||||||||
import { serve } from "bun"; | ||||||||||||||||||||
import { describe } from "bun:test"; | ||||||||||||||||||||
import { describe, expect, it } from "bun:test"; | ||||||||||||||||||||
import { | ||||||||||||||||||||
createJSONResponse, | ||||||||||||||||||||
findResourceInstance, | ||||||||||||||||||||
runTerraformInit, | ||||||||||||||||||||
runTerraformApply, | ||||||||||||||||||||
testRequiredVariables, | ||||||||||||||||||||
} from "../test"; | ||||||||||||||||||||
|
||||||||||||||||||||
describe("jfrog-oauth", async () => { | ||||||||||||||||||||
await runTerraformInit(import.meta.dir); | ||||||||||||||||||||
|
||||||||||||||||||||
testRequiredVariables(import.meta.dir, { | ||||||||||||||||||||
agent_id: "some-agent-id", | ||||||||||||||||||||
jfrog_url: "http://localhost:8081", | ||||||||||||||||||||
package_managers: "{}", | ||||||||||||||||||||
const fakeFrogHostAndPort = "localhost:8081"; | ||||||||||||||||||||
const fakeFrogUrl = `http://${fakeFrogHostAndPort}`; | ||||||||||||||||||||
|
||||||||||||||||||||
it("can run apply with required variables", async () => { | ||||||||||||||||||||
testRequiredVariables(import.meta.dir, { | ||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just to provide a little more type-safety, could you define a custom type for the variables, and pass it along as a type parameter? type TestVariables = Readonly<{
agent_id: string;
jfrog_url: string;
package_managers: string;
username_field?: string;
jfrog_server_id?: string;
external_auth_id: string;
configure_code_server?: boolean;
}>; You'd then be able to pass this type to both runTerraformApply<TestVariables>(/* Same arguments as before */) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I appreciate the concept here but please allow me to push back slightly on this suggestion. I fear it might be redundant to define the variables in the test with minimal value because:
In summary, I think you are only validating the test code is type safe and not really validating the logic of the module. I don't see this pattern in many, if any, of the other tests for modules but if you are adamant that this is the correct thing for your code, I will implement it. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I put this in place in the interest of getting these changes merged. I do think it feels contrived since you have to define the type and then make sure you specify the type as a type param for the various function calls to get the benefit of enforcing the type check. It might be a tighter workflow if you had a generic Testing class instead of the free functions in describe("some-module", async () => {
type SomeVariables = {
...
};
const test = new Testing<SomeVariables>();
const state = await test.runTerraformApply(...);
...
}); Obviously a change outside the scope of this PR, but does that make sense to you? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sorry, was a little swamped these past few days, and haven't had a chance to respond earlier On one hand, I guess I view the current setup as not that different from other JavaScript values like Maps or Sets. If you have something like this: const userMap = Map<string, User>(); I don't think that's too different from one of the testing functions. The type restrictions don't exist at runtime, so non-user values can still accidentally be placed in the But I think you're right, and I think the current type behavior is a bit of a bandaid. These are just test files, so them breaking on typos isn't as huge of a deal, because you get instant feedback before they bubble up to an end-user But going to your idea about the generic testing class, that does seem like a really good idea. The reason why having There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think I'll actually make a separate PR once this one is done to leverage classes more |
||||||||||||||||||||
agent_id: "some-agent-id", | ||||||||||||||||||||
jfrog_url: fakeFrogUrl, | ||||||||||||||||||||
package_managers: "{}", | ||||||||||||||||||||
}); | ||||||||||||||||||||
}); | ||||||||||||||||||||
}); | ||||||||||||||||||||
|
||||||||||||||||||||
//TODO add more tests | ||||||||||||||||||||
it("generates an npmrc with scoped repos", async () => { | ||||||||||||||||||||
const state = await runTerraformApply(import.meta.dir, { | ||||||||||||||||||||
agent_id: "some-agent-id", | ||||||||||||||||||||
jfrog_url: fakeFrogUrl, | ||||||||||||||||||||
package_managers: JSON.stringify({ | ||||||||||||||||||||
npm: ["global", "@foo:foo", "@bar:bar"], | ||||||||||||||||||||
}), | ||||||||||||||||||||
}); | ||||||||||||||||||||
const coderScript = findResourceInstance(state, "coder_script"); | ||||||||||||||||||||
const npmrcStanza = `cat << EOF > ~/.npmrc | ||||||||||||||||||||
[email protected] | ||||||||||||||||||||
registry=${fakeFrogUrl}/artifactory/api/npm/global | ||||||||||||||||||||
//${fakeFrogHostAndPort}/artifactory/api/npm/global/:_authToken= | ||||||||||||||||||||
@foo:registry=${fakeFrogUrl}/artifactory/api/npm/foo | ||||||||||||||||||||
//${fakeFrogHostAndPort}/artifactory/api/npm/foo/:_authToken= | ||||||||||||||||||||
@bar:registry=${fakeFrogUrl}/artifactory/api/npm/bar | ||||||||||||||||||||
//${fakeFrogHostAndPort}/artifactory/api/npm/bar/:_authToken= | ||||||||||||||||||||
|
||||||||||||||||||||
EOF`; | ||||||||||||||||||||
expect(coderScript.script).toContain(npmrcStanza); | ||||||||||||||||||||
expect(coderScript.script).toContain( | ||||||||||||||||||||
'jf npmc --global --repo-resolve "global"', | ||||||||||||||||||||
); | ||||||||||||||||||||
expect(coderScript.script).toContain( | ||||||||||||||||||||
'if [ -z "YES" ]; then\n not_configured npm', | ||||||||||||||||||||
); | ||||||||||||||||||||
}); | ||||||||||||||||||||
|
||||||||||||||||||||
it("generates a pip config with extra-indexes", async () => { | ||||||||||||||||||||
const state = await runTerraformApply(import.meta.dir, { | ||||||||||||||||||||
agent_id: "some-agent-id", | ||||||||||||||||||||
jfrog_url: fakeFrogUrl, | ||||||||||||||||||||
package_managers: JSON.stringify({ | ||||||||||||||||||||
pypi: ["global", "foo", "bar"], | ||||||||||||||||||||
}), | ||||||||||||||||||||
}); | ||||||||||||||||||||
const coderScript = findResourceInstance(state, "coder_script"); | ||||||||||||||||||||
const pipStanza = `cat << EOF > ~/.pip/pip.conf | ||||||||||||||||||||
[global] | ||||||||||||||||||||
index-url = https://default:@${fakeFrogHostAndPort}/artifactory/api/pypi/global/simple | ||||||||||||||||||||
extra-index-url = | ||||||||||||||||||||
https://default:@${fakeFrogHostAndPort}/artifactory/api/pypi/foo/simple | ||||||||||||||||||||
https://default:@${fakeFrogHostAndPort}/artifactory/api/pypi/bar/simple | ||||||||||||||||||||
|
||||||||||||||||||||
EOF`; | ||||||||||||||||||||
expect(coderScript.script).toContain(pipStanza); | ||||||||||||||||||||
expect(coderScript.script).toContain( | ||||||||||||||||||||
'jf pipc --global --repo-resolve "global"', | ||||||||||||||||||||
); | ||||||||||||||||||||
expect(coderScript.script).toContain( | ||||||||||||||||||||
'if [ -z "YES" ]; then\n not_configured pypi', | ||||||||||||||||||||
); | ||||||||||||||||||||
}); | ||||||||||||||||||||
|
||||||||||||||||||||
it("registers multiple docker repos", async () => { | ||||||||||||||||||||
const state = await runTerraformApply(import.meta.dir, { | ||||||||||||||||||||
agent_id: "some-agent-id", | ||||||||||||||||||||
jfrog_url: fakeFrogUrl, | ||||||||||||||||||||
package_managers: JSON.stringify({ | ||||||||||||||||||||
docker: ["foo.jfrog.io", "bar.jfrog.io", "baz.jfrog.io"], | ||||||||||||||||||||
}), | ||||||||||||||||||||
Comment on lines
+93
to
+95
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This definitely isn't your fault, but I'm making a note so I don't forget: we should update our helper functions so that they support Terraform objects directly |
||||||||||||||||||||
}); | ||||||||||||||||||||
const coderScript = findResourceInstance(state, "coder_script"); | ||||||||||||||||||||
const dockerStanza = `register_docker "foo.jfrog.io" | ||||||||||||||||||||
register_docker "bar.jfrog.io" | ||||||||||||||||||||
register_docker "baz.jfrog.io"`; | ||||||||||||||||||||
expect(coderScript.script).toContain(dockerStanza); | ||||||||||||||||||||
expect(coderScript.script).toContain( | ||||||||||||||||||||
'if [ -z "YES" ]; then\n not_configured docker', | ||||||||||||||||||||
); | ||||||||||||||||||||
}); | ||||||||||||||||||||
|
||||||||||||||||||||
it("sets goproxy with multiple repos", async () => { | ||||||||||||||||||||
const state = await runTerraformApply(import.meta.dir, { | ||||||||||||||||||||
agent_id: "some-agent-id", | ||||||||||||||||||||
jfrog_url: fakeFrogUrl, | ||||||||||||||||||||
package_managers: JSON.stringify({ | ||||||||||||||||||||
go: ["foo", "bar", "baz"], | ||||||||||||||||||||
}), | ||||||||||||||||||||
}); | ||||||||||||||||||||
const proxyEnv = findResourceInstance(state, "coder_env", "goproxy"); | ||||||||||||||||||||
const proxies = `https://default:@${fakeFrogHostAndPort}/artifactory/api/go/foo,https://default:@${fakeFrogHostAndPort}/artifactory/api/go/bar,https://default:@${fakeFrogHostAndPort}/artifactory/api/go/baz`; | ||||||||||||||||||||
expect(proxyEnv["value"]).toEqual(proxies); | ||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I feel like this might be a little more readable:
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. How about this: ["foo","bar","baz"].map(r => `https://default:@${fakeFrogHostAndPort}/artifactory/api/go/${r}`).join(","); There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, that works, too |
||||||||||||||||||||
|
||||||||||||||||||||
const coderScript = findResourceInstance(state, "coder_script"); | ||||||||||||||||||||
expect(coderScript.script).toContain( | ||||||||||||||||||||
'jf goc --global --repo-resolve "foo"', | ||||||||||||||||||||
); | ||||||||||||||||||||
expect(coderScript.script).toContain( | ||||||||||||||||||||
'if [ -z "YES" ]; then\n not_configured go', | ||||||||||||||||||||
); | ||||||||||||||||||||
}); | ||||||||||||||||||||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
[global] | ||
index-url = https://${ARTIFACTORY_USERNAME}:${ARTIFACTORY_ACCESS_TOKEN}@${JFROG_HOST}/artifactory/api/pypi/${try(element(REPOS, 0), "")}/simple | ||
extra-index-url = | ||
%{ for REPO in try(slice(REPOS, 1, length(REPOS)), []) ~} | ||
https://${ARTIFACTORY_USERNAME}:${ARTIFACTORY_ACCESS_TOKEN}@${JFROG_HOST}/artifactory/api/pypi/${REPO}/simple | ||
%{ endfor ~} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@matifali I'm still not fully clear on how we're handling versioning, but we would want this to be version
1.0.19
?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes, we will go with 1.0.19 for now until we resolve #157.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok. I was bumping the major version since the input variable format for the repos is a breaking change, but if there's an outside constraint, I'll update the readme to reflect the target version of 1.0.19
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is indeed a breaking change but we do not want to bump all future modules to 2.0.0 before we do #157.