Skip to content

Commit 14e6def

Browse files
committed
jfrogGH-621 - working for groups, users and repos.
next steps: handle the special case of docker local repos (we need to fetch the version number which requires an extra call) test management of netrc (we don't want to mess up someones file) exclude resources found in the local terraform state
1 parent a5ecbd6 commit 14e6def

File tree

4 files changed

+282
-48
lines changed

4 files changed

+282
-48
lines changed

Dockerfile

Lines changed: 34 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,35 @@
1-
# Fetch the dependencies
2-
FROM golang:1.15-alpine AS builder
3-
4-
RUN apk add --update ca-certificates git gcc g++ libc-dev
5-
WORKDIR /src/
6-
7-
ENV GO111MODULE=on
8-
9-
COPY go.mod .
10-
COPY go.sum .
11-
1+
FROM alpine AS base
2+
RUN apk add --no-cache git terraform wget make openssh && \
3+
wget -q https://github.com/goreleaser/goreleaser/releases/download/v1.19.2/goreleaser_1.19.2_x86_64.apk && \
4+
wget -q https://go.dev/dl/go1.19.2.linux-amd64.tar.gz && \
5+
rm -rf /usr/local/go && \
6+
tar -C /usr/local -xzf go1.19.2.linux-amd64.tar.gz && \
7+
apk add --allow-untrusted goreleaser_1.19.2_x86_64.apk && \
8+
mkdir -p /src/terraform-provider-artifactory
9+
ENV PATH=$PATH:/usr/local/go/bin
10+
WORKDIR /root
11+
12+
FROM base as builder
13+
COPY . .
1214
RUN go mod download
13-
14-
COPY pkg/ /src/pkg/
15-
COPY main.go /src/
16-
17-
RUN CGO_ENABLED=0 GOOS=linux go build
18-
19-
20-
# Build the final image
21-
FROM hashicorp/terraform:0.13
22-
23-
COPY --from=builder /src/terraform-provider-artifactory /root/.terraform.d/plugins/
15+
RUN make
16+
WORKDIR /root/v5-v6-migrator
17+
RUN make
18+
19+
FROM hashicorp/terraform as plugin
20+
RUN adduser -S jfrog
21+
WORKDIR /home/jfrog
22+
COPY --from=builder /src/terraform-provider-artifactory/terraform-provider-artifactory /home/jfrog/.terraform.d/plugins/
23+
24+
FROM alpine as migrator
25+
RUN adduser -S jfrog
26+
WORKDIR /home/jfrog
27+
COPY --from=builder /src/terraform-provider-artifactory/v5-v6-migrator/tf-v5-migrator /home/jfrog/tf-v5-migrator
28+
29+
FROM alpine as importer
30+
RUN apk add --no-cache jq curl bash
31+
RUN adduser -S jfrog
32+
WORKDIR /home/jfrog
33+
USER jfrog
34+
COPY scripts/bulkimport.sh .
35+
ENTRYPOINT ./bulkimport.sh

sample.tf

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ terraform {
33
required_providers {
44
artifactory = {
55
source = "registry.terraform.io/jfrog/artifactory"
6-
version = "7.10.1"
6+
version = "8.9.2"
77
}
88
}
99
}

scripts/bulkimport.sh

Lines changed: 232 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,240 @@
11
#!/usr/bin/env bash
2+
${DEBUG:+set -x}
3+
set -e
4+
set -o errexit -o pipefail -o noclobber -o nounset
25

6+
HOME=${HOME:-"~"}
37

4-
function importRepos {
5-
while read -r key type package; do
6-
cat <<-EOF
7-
import {
8-
to = artifactory_${type,,}_${package,,}_repository.${key,,}
9-
id = "${key,,}"
10-
}
11-
EOF
12-
done < <(curl -snLf https://partnerenttest.jfrog.io/artifactory/api/repositories | jq -re '.[] | "\(.key) \(.type) \(.packageType)"' )
13-
} && export -f importRepos
14-
15-
function importUsers {
16-
for i in {1..10}; do
17-
local username="username-${RANDOM}-${i}"
18-
cat <<-EOF
19-
import {
20-
to = artifactory_user.${username}
21-
id = %s
22-
}
23-
EOF
24-
done
25-
}
8+
command -v curl >/dev/null || (echo "You must install curl to proceed" >&2 && exit 1)
9+
command -v jq >/dev/null || (echo "You must install jq to proceed" >&2 && exit 1)
10+
command -v terraform >/dev/null || (echo "You must install terraform to proceed" >&2 && exit 1)
2611

12+
function usage {
13+
echo "${0} [{--users |-u} {--repos|-r} | {--groups|-g}] -h|--host https://\${host}" >&2
14+
echo "duplicate resource declarations are de duped (see below)"
15+
echo "example: ${0} -u --repos --users -h https://myartifactory.com > import.tf"
16+
exit 1
17+
}
2718

28-
resources=(importRepos importUsers importRepos)
29-
for f in $(echo "${resources[@]}" | tr ' ' '\n' | sort -u | tr '\n' ' '); do
30-
eval "${f}"
19+
resources=()
20+
while getopts urdga:-: OPT; do
21+
# shellcheck disable=SC2154
22+
if [ "$OPT" = "-" ]; then # long option: reformulate OPT and OPTARG
23+
OPT="${OPTARG%%=*}" # extract long option name
24+
OPTARG="${OPTARG#$OPT}" # extract long option argument (may be empty)
25+
OPTARG="${OPTARG#=}" # if long option argument, remove assigning `=`
26+
fi
27+
# shellcheck disable=SC2214
28+
case "${OPT}" in
29+
u | users)
30+
resources+=(users)
31+
;;
32+
r | repos)
33+
resources+=(repos)
34+
;;
35+
g | groups)
36+
resources+=(groups)
37+
;;
38+
a | artifactory-url)
39+
host="${OPTARG}"
40+
grep -qE 'http(s)?://.*' <<<"${host}" || (echo "malformed url name: ${host}. must be of the form http(s)?://.*" && exit 1)
41+
;;
42+
??*)
43+
usage
44+
;;
45+
*)
46+
usage
47+
;;
48+
esac
3149
done
3250

51+
function netrc_location {
52+
case "$(uname)" in
53+
CYGWIN* | MSYS* | MINGW*)
54+
echo "$HOME/_netrc"
55+
;;
56+
Darwin* | Linux*)
57+
echo "$HOME/.netrc"
58+
;;
59+
*)
60+
echo "unsupported OS" >&2
61+
return 255
62+
;;
63+
esac
64+
}
65+
function assert_netrc {
66+
local location
67+
location="$(netrc_location)"
68+
test -f "${location}" || touch "${location}"
69+
echo "${location}"
70+
}
71+
72+
function hasNetRcEntry {
73+
local h="${1:?No host supplied}"
74+
75+
h="${h/https:\/\//}"
76+
h="${h/http:\/\//}"
77+
grep -qE "machine[ ]+${h}" "$(assert_netrc)"
78+
}
79+
80+
function write_netrc {
81+
local host="${1:?You must supply a host name}"
82+
local netrc
83+
netrc=$(assert_netrc)
84+
read -r -p "please enter the username for ${host}: " username
85+
read -rs -p "enter the api token (will not be echoed): " token
86+
# append only to both files
87+
cat <<-EOF >> "${netrc}"
88+
machine ${host}
89+
login ${username}
90+
password ${token}
91+
EOF
92+
echo "${host}"
93+
}
94+
95+
# if they have no netrc file at all, create the file and add an entry
96+
if ! hasNetRcEntry "${host}" ; then
97+
echo "added entry to $(write_netrc "${host}" ) needed for curl" >&2
98+
fi
99+
100+
function repos {
101+
# jq '.resources |map({type,name})' terraform.tfstate # make sure to not include anything already in state
102+
# we'll make our internal jq structure match that of the tf state file so we can subtract them easy
103+
local host="${1:?You must supply the artifactory host}"
104+
# literally, usage of jq is 6x faster than bash/read/etc
105+
# GET "${host}/artifactory/api/repositories" returns {key,type,packageType,url} where
106+
# url) points to the UI for that resource??
107+
# packageType) is cased ??
108+
# type) is upcased and in "${host}/artifactory/api/repositories/${key}" it's not AND it's called rclass
109+
local url="${host}/artifactory/api/repositories"
110+
curl -snLf "${url}" | jq -re --arg u "${url}" '.[] | "\($u)/\(.key)"' |
111+
xargs -P 10 curl -snLf |
112+
jq -sre '
113+
group_by(.packageType == "docker" and .rclass == "local") |
114+
(.[0] | map({
115+
type: "artifactory_\(.rclass)_\(.packageType)_repository.\(.key)",
116+
name: key
117+
})
118+
) +
119+
(.[1] | map({
120+
type: "artifactory_\(.rclass | ascii_downcase)_\(.packageType | ascii_downcase)_\(.dockerApiVersion | ascii_downcase)_repository.\(.key)",
121+
name: key
122+
})
123+
) | .[] |
124+
"import {
125+
to = \(.type)
126+
id = \"\(.name)\"
127+
}"'
128+
} && export -f repos
129+
130+
function accessTokens {
131+
local host="${1:?You must supply the artifactory host}"
132+
return 1
133+
curl -snLf "${host}/artifactory/api/repositories/artifactory/api/security/token"
134+
}
135+
136+
function ldapGroups {
137+
local host="${1:?You must supply the artifactory host}"
138+
return 1
139+
curl -snLf "${host}/access/api/v1/ldap/groups"
140+
}
141+
142+
function apiKeys {
143+
local host="${1:?You must supply the artifactory host}"
144+
return 1
145+
curl -snLf "${host}/artifactory/api/security/apiKey"
146+
147+
}
148+
149+
function groups {
150+
local host="${1:?You must supply the artifactory host}"
151+
curl -snLf "${host}/artifactory/api/security/groups" |
152+
jq -re '.[].name |
153+
"import {
154+
to = artifactory_group.\(.)
155+
id = \"\(.)\"
156+
}"'
157+
}
158+
159+
function certificates {
160+
local host="${1:?You must supply the artifactory host}"
161+
curl -snLf "${host}/artifactory/api/system/security/certificates/" |
162+
jq -re '.[] |
163+
"import {
164+
to = artifactory_certificate.\(.certificateAlias)
165+
id = \"(.certificateAlias)\"
166+
}"'
167+
}
168+
169+
function distributionPublicKeys {
170+
local host="${1:?You must supply the artifactory host}"
171+
# untested
172+
return 1
173+
curl -snLf "${host}/artifactory/api/security/keys/trusted" |
174+
jq -re '.keys[] | "
175+
import {
176+
to = artifactory_distribution_public_key.\(.alias)
177+
id = \"\(.kid)\"
178+
}"'
179+
}
180+
181+
function permissions {
182+
# these names have spaces in them
183+
local host="${1:?You must supply the artifactory host}"
184+
return 1 # untested
185+
curl -snLf "${host}/artifactory/api/v2/security/permissions/" |
186+
jq -re '.[] | select(.name | startswith("INTERNAL") | not) | "
187+
import {
188+
to = artifactory_permission_target.\(.name)
189+
id = \"\(.name)\"
190+
}"'
191+
}
192+
function keyPairs {
193+
local host="${1:?You must supply the artifactory host}"
194+
return 1 # untested
195+
curl -snLf "${host}/artifactory/api/security/keypair/" |
196+
jq -re '.[] | "
197+
import {
198+
to = artifactory_keypair.\(.pairName)
199+
id = \"\(.pairName)\"
200+
}"'
201+
}
202+
function users {
203+
# .name has values in it that artifactory will never accept, like email@. Not sure if in that case it should just be user-$RANDOM
204+
local host="${1:?You must supply the artifactory host}"
205+
curl -snLf "${host}/artifactory/api/security/users" | jq -re '.[] |
206+
{user: .name | capture("(?<user>\\w+)@(?<domain>\\w+)").user, name}|
207+
"import {
208+
to = artifactory_user.\(.user)
209+
id = \"\(.name)\"
210+
}"'
211+
}
212+
213+
214+
function output {
215+
local host="${1:?You must supply artifactory host name}"
216+
# don't touch this heredoc if you want proper output format
217+
cat <<-EOF
218+
terraform {
219+
required_providers {
220+
artifactory = {
221+
source = "registry.terraform.io/jfrog/artifactory"
222+
version = ">= 9.1.0"
223+
}
224+
}
225+
}
226+
provider "artifactory" {
227+
url = "${host}"
228+
}
229+
230+
$(for f in "${@:2}"; do
231+
eval "${f} ${host}"
232+
done)
233+
EOF
234+
}
33235

236+
# shellcheck disable=SC2046
237+
output "${host}" $(echo "${resources[@]}" | tr ' ' '\n' | sort -u | tr '\n' ' ')
238+
# out="$RANDOM-out"
239+
#terraform plan -generate-config-out generated.tf -out "${out} -parallelism=10
240+
#terraform apply -parallelism=10

scripts/repodata.jq

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
group_by(.packageType == "docker" and .rclass == "local") |
2+
(.[0] | map({
3+
to: "artifactory_\(.rclass)_\(.packageType)_repository.\(.key)",
4+
key
5+
})
6+
) +
7+
(.[1] | map({
8+
to: "artifactory_\(.rclass | ascii_downcase)_\(.packageType | ascii_downcase)_\(.dockerApiVersion | ascii_downcase)_repository.\(.key)",
9+
key
10+
})
11+
) | .[] |
12+
"import {
13+
to = \(.to)
14+
id = \"\(.key)\"
15+
}"

0 commit comments

Comments
 (0)