From 9971e19a805c612e7ecd3dba5eee755a621c4168 Mon Sep 17 00:00:00 2001 From: Christian Bongiorno Date: Mon, 25 Sep 2023 17:22:57 -0700 Subject: [PATCH 01/10] trying out spaces --- .space/devfile.yaml | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 .space/devfile.yaml diff --git a/.space/devfile.yaml b/.space/devfile.yaml new file mode 100644 index 00000000..e04694a2 --- /dev/null +++ b/.space/devfile.yaml @@ -0,0 +1,13 @@ +schemaVersion: 2.2.0 +attributes: + space: + instanceType: large + editor: + type: Idea +components: + - name: image-build + image: + # (Required) + imageName: jfrog:latest + dockerfile: + uri: . From aed70815a20dcbc9732000b76ba9e518d9be8fc9 Mon Sep 17 00:00:00 2001 From: Christian Bongiorno Date: Mon, 25 Sep 2023 17:27:25 -0700 Subject: [PATCH 02/10] Update devfile.yaml --- .space/devfile.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.space/devfile.yaml b/.space/devfile.yaml index e04694a2..62bfc1f2 100644 --- a/.space/devfile.yaml +++ b/.space/devfile.yaml @@ -3,7 +3,7 @@ attributes: space: instanceType: large editor: - type: Idea + type: GoLand components: - name: image-build image: From 14e6def1f2615567f71073fad3a4486449377c0e Mon Sep 17 00:00:00 2001 From: Christian Bongiorno Date: Sun, 1 Oct 2023 11:19:51 -0700 Subject: [PATCH 03/10] GH-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 --- Dockerfile | 56 +++++---- sample.tf | 2 +- scripts/bulkimport.sh | 257 ++++++++++++++++++++++++++++++++++++++---- scripts/repodata.jq | 15 +++ 4 files changed, 282 insertions(+), 48 deletions(-) create mode 100644 scripts/repodata.jq diff --git a/Dockerfile b/Dockerfile index b32039ee..bb77f5b6 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,23 +1,35 @@ -# Fetch the dependencies -FROM golang:1.15-alpine AS builder - -RUN apk add --update ca-certificates git gcc g++ libc-dev -WORKDIR /src/ - -ENV GO111MODULE=on - -COPY go.mod . -COPY go.sum . - +FROM alpine AS base +RUN apk add --no-cache git terraform wget make openssh && \ + wget -q https://github.com/goreleaser/goreleaser/releases/download/v1.19.2/goreleaser_1.19.2_x86_64.apk && \ + wget -q https://go.dev/dl/go1.19.2.linux-amd64.tar.gz && \ + rm -rf /usr/local/go && \ + tar -C /usr/local -xzf go1.19.2.linux-amd64.tar.gz && \ + apk add --allow-untrusted goreleaser_1.19.2_x86_64.apk && \ + mkdir -p /src/terraform-provider-artifactory +ENV PATH=$PATH:/usr/local/go/bin +WORKDIR /root + +FROM base as builder +COPY . . RUN go mod download - -COPY pkg/ /src/pkg/ -COPY main.go /src/ - -RUN CGO_ENABLED=0 GOOS=linux go build - - -# Build the final image -FROM hashicorp/terraform:0.13 - -COPY --from=builder /src/terraform-provider-artifactory /root/.terraform.d/plugins/ +RUN make +WORKDIR /root/v5-v6-migrator +RUN make + +FROM hashicorp/terraform as plugin +RUN adduser -S jfrog +WORKDIR /home/jfrog +COPY --from=builder /src/terraform-provider-artifactory/terraform-provider-artifactory /home/jfrog/.terraform.d/plugins/ + +FROM alpine as migrator +RUN adduser -S jfrog +WORKDIR /home/jfrog +COPY --from=builder /src/terraform-provider-artifactory/v5-v6-migrator/tf-v5-migrator /home/jfrog/tf-v5-migrator + +FROM alpine as importer +RUN apk add --no-cache jq curl bash +RUN adduser -S jfrog +WORKDIR /home/jfrog +USER jfrog +COPY scripts/bulkimport.sh . +ENTRYPOINT ./bulkimport.sh diff --git a/sample.tf b/sample.tf index e493a783..31c2c86e 100644 --- a/sample.tf +++ b/sample.tf @@ -3,7 +3,7 @@ terraform { required_providers { artifactory = { source = "registry.terraform.io/jfrog/artifactory" - version = "7.10.1" + version = "8.9.2" } } } diff --git a/scripts/bulkimport.sh b/scripts/bulkimport.sh index f8bdb0ad..59f63b2f 100755 --- a/scripts/bulkimport.sh +++ b/scripts/bulkimport.sh @@ -1,33 +1,240 @@ #!/usr/bin/env bash +${DEBUG:+set -x} +set -e +set -o errexit -o pipefail -o noclobber -o nounset +HOME=${HOME:-"~"} -function importRepos { - while read -r key type package; do - cat <<-EOF - import { - to = artifactory_${type,,}_${package,,}_repository.${key,,} - id = "${key,,}" - } - EOF - done < <(curl -snLf https://partnerenttest.jfrog.io/artifactory/api/repositories | jq -re '.[] | "\(.key) \(.type) \(.packageType)"' ) -} && export -f importRepos - -function importUsers { - for i in {1..10}; do - local username="username-${RANDOM}-${i}" - cat <<-EOF - import { - to = artifactory_user.${username} - id = %s - } - EOF - done -} +command -v curl >/dev/null || (echo "You must install curl to proceed" >&2 && exit 1) +command -v jq >/dev/null || (echo "You must install jq to proceed" >&2 && exit 1) +command -v terraform >/dev/null || (echo "You must install terraform to proceed" >&2 && exit 1) +function usage { + echo "${0} [{--users |-u} {--repos|-r} | {--groups|-g}] -h|--host https://\${host}" >&2 + echo "duplicate resource declarations are de duped (see below)" + echo "example: ${0} -u --repos --users -h https://myartifactory.com > import.tf" + exit 1 +} -resources=(importRepos importUsers importRepos) -for f in $(echo "${resources[@]}" | tr ' ' '\n' | sort -u | tr '\n' ' '); do - eval "${f}" +resources=() +while getopts urdga:-: OPT; do + # shellcheck disable=SC2154 + if [ "$OPT" = "-" ]; then # long option: reformulate OPT and OPTARG + OPT="${OPTARG%%=*}" # extract long option name + OPTARG="${OPTARG#$OPT}" # extract long option argument (may be empty) + OPTARG="${OPTARG#=}" # if long option argument, remove assigning `=` + fi + # shellcheck disable=SC2214 + case "${OPT}" in + u | users) + resources+=(users) + ;; + r | repos) + resources+=(repos) + ;; + g | groups) + resources+=(groups) + ;; + a | artifactory-url) + host="${OPTARG}" + grep -qE 'http(s)?://.*' <<<"${host}" || (echo "malformed url name: ${host}. must be of the form http(s)?://.*" && exit 1) + ;; + ??*) + usage + ;; + *) + usage + ;; + esac done +function netrc_location { + case "$(uname)" in + CYGWIN* | MSYS* | MINGW*) + echo "$HOME/_netrc" + ;; + Darwin* | Linux*) + echo "$HOME/.netrc" + ;; + *) + echo "unsupported OS" >&2 + return 255 + ;; + esac +} +function assert_netrc { + local location + location="$(netrc_location)" + test -f "${location}" || touch "${location}" + echo "${location}" +} + +function hasNetRcEntry { + local h="${1:?No host supplied}" + + h="${h/https:\/\//}" + h="${h/http:\/\//}" + grep -qE "machine[ ]+${h}" "$(assert_netrc)" +} + +function write_netrc { + local host="${1:?You must supply a host name}" + local netrc + netrc=$(assert_netrc) + read -r -p "please enter the username for ${host}: " username + read -rs -p "enter the api token (will not be echoed): " token + # append only to both files + cat <<-EOF >> "${netrc}" + machine ${host} + login ${username} + password ${token} + EOF + echo "${host}" +} + +# if they have no netrc file at all, create the file and add an entry +if ! hasNetRcEntry "${host}" ; then + echo "added entry to $(write_netrc "${host}" ) needed for curl" >&2 +fi + +function repos { + # jq '.resources |map({type,name})' terraform.tfstate # make sure to not include anything already in state + # we'll make our internal jq structure match that of the tf state file so we can subtract them easy + local host="${1:?You must supply the artifactory host}" + # literally, usage of jq is 6x faster than bash/read/etc + # GET "${host}/artifactory/api/repositories" returns {key,type,packageType,url} where + # url) points to the UI for that resource?? + # packageType) is cased ?? + # type) is upcased and in "${host}/artifactory/api/repositories/${key}" it's not AND it's called rclass + local url="${host}/artifactory/api/repositories" + curl -snLf "${url}" | jq -re --arg u "${url}" '.[] | "\($u)/\(.key)"' | + xargs -P 10 curl -snLf | + jq -sre ' + group_by(.packageType == "docker" and .rclass == "local") | + (.[0] | map({ + type: "artifactory_\(.rclass)_\(.packageType)_repository.\(.key)", + name: key + }) + ) + + (.[1] | map({ + type: "artifactory_\(.rclass | ascii_downcase)_\(.packageType | ascii_downcase)_\(.dockerApiVersion | ascii_downcase)_repository.\(.key)", + name: key + }) + ) | .[] | +"import { + to = \(.type) + id = \"\(.name)\" +}"' +} && export -f repos + +function accessTokens { + local host="${1:?You must supply the artifactory host}" + return 1 + curl -snLf "${host}/artifactory/api/repositories/artifactory/api/security/token" +} + +function ldapGroups { + local host="${1:?You must supply the artifactory host}" + return 1 + curl -snLf "${host}/access/api/v1/ldap/groups" +} + +function apiKeys { + local host="${1:?You must supply the artifactory host}" + return 1 + curl -snLf "${host}/artifactory/api/security/apiKey" + +} + +function groups { + local host="${1:?You must supply the artifactory host}" + curl -snLf "${host}/artifactory/api/security/groups" | + jq -re '.[].name | + "import { + to = artifactory_group.\(.) + id = \"\(.)\" +}"' +} + +function certificates { + local host="${1:?You must supply the artifactory host}" + curl -snLf "${host}/artifactory/api/system/security/certificates/" | + jq -re '.[] | +"import { + to = artifactory_certificate.\(.certificateAlias) + id = \"(.certificateAlias)\" +}"' +} + +function distributionPublicKeys { + local host="${1:?You must supply the artifactory host}" + # untested + return 1 + curl -snLf "${host}/artifactory/api/security/keys/trusted" | + jq -re '.keys[] | " +import { + to = artifactory_distribution_public_key.\(.alias) + id = \"\(.kid)\" +}"' +} + +function permissions { + # these names have spaces in them + local host="${1:?You must supply the artifactory host}" + return 1 # untested + curl -snLf "${host}/artifactory/api/v2/security/permissions/" | + jq -re '.[] | select(.name | startswith("INTERNAL") | not) | " +import { + to = artifactory_permission_target.\(.name) + id = \"\(.name)\" +}"' +} +function keyPairs { + local host="${1:?You must supply the artifactory host}" + return 1 # untested + curl -snLf "${host}/artifactory/api/security/keypair/" | + jq -re '.[] | " +import { + to = artifactory_keypair.\(.pairName) + id = \"\(.pairName)\" +}"' +} +function users { + # .name has values in it that artifactory will never accept, like email@. Not sure if in that case it should just be user-$RANDOM + local host="${1:?You must supply the artifactory host}" + curl -snLf "${host}/artifactory/api/security/users" | jq -re '.[] | + {user: .name | capture("(?\\w+)@(?\\w+)").user, name}| + "import { + to = artifactory_user.\(.user) + id = \"\(.name)\" +}"' +} + + +function output { + local host="${1:?You must supply artifactory host name}" +# don't touch this heredoc if you want proper output format + cat <<-EOF + terraform { + required_providers { + artifactory = { + source = "registry.terraform.io/jfrog/artifactory" + version = ">= 9.1.0" + } + } + } + provider "artifactory" { + url = "${host}" + } + + $(for f in "${@:2}"; do + eval "${f} ${host}" + done) + EOF +} +# shellcheck disable=SC2046 +output "${host}" $(echo "${resources[@]}" | tr ' ' '\n' | sort -u | tr '\n' ' ') +# out="$RANDOM-out" +#terraform plan -generate-config-out generated.tf -out "${out} -parallelism=10 +#terraform apply -parallelism=10 diff --git a/scripts/repodata.jq b/scripts/repodata.jq new file mode 100644 index 00000000..93fb3870 --- /dev/null +++ b/scripts/repodata.jq @@ -0,0 +1,15 @@ +group_by(.packageType == "docker" and .rclass == "local") | +(.[0] | map({ + to: "artifactory_\(.rclass)_\(.packageType)_repository.\(.key)", + key + }) +) + +(.[1] | map({ + to: "artifactory_\(.rclass | ascii_downcase)_\(.packageType | ascii_downcase)_\(.dockerApiVersion | ascii_downcase)_repository.\(.key)", + key + }) +) | .[] | +"import { + to = \(.to) + id = \"\(.key)\" +}" From 6482ab839782cf09204a9f6d68d6efde80acde6d Mon Sep 17 00:00:00 2001 From: Christian Bongiorno Date: Mon, 2 Oct 2023 16:15:33 -0700 Subject: [PATCH 04/10] GH-621 - all done. No support for docker v1 at all. --- scripts/bulkimport.sh | 128 +++++++++++++++++++++++++----------------- scripts/repodata.jq | 15 ----- 2 files changed, 78 insertions(+), 65 deletions(-) delete mode 100644 scripts/repodata.jq diff --git a/scripts/bulkimport.sh b/scripts/bulkimport.sh index 59f63b2f..95ee4170 100755 --- a/scripts/bulkimport.sh +++ b/scripts/bulkimport.sh @@ -2,6 +2,10 @@ ${DEBUG:+set -x} set -e set -o errexit -o pipefail -o noclobber -o nounset +shopt -s expand_aliases + +# shellcheck disable=SC2139 +alias curl="curl -snL${DEBUG:+v}f" HOME=${HOME:-"~"} @@ -10,14 +14,20 @@ command -v jq >/dev/null || (echo "You must install jq to proceed" >&2 && exit 1 command -v terraform >/dev/null || (echo "You must install terraform to proceed" >&2 && exit 1) function usage { - echo "${0} [{--users |-u} {--repos|-r} | {--groups|-g}] -h|--host https://\${host}" >&2 - echo "duplicate resource declarations are de duped (see below)" - echo "example: ${0} -u --repos --users -h https://myartifactory.com > import.tf" + echo "${0} [{--users |-u} {--repos|-r} | {--groups|-g} | {--all | -a}] -h|--host https://\${host} + duplicate resource declarations are de duped (see below) + + example: + ${0} -u --repos --users -h https://myartifactory.com > import.tf + terraform plan -no-color -generate-config-out generated.tf -out ${RANDOM}-out -parallelism=10 + terraform apply -no-color -parallelism=10 + + You may enable debug with: DEBUG=1 ${0} ..." >&2 exit 1 } resources=() -while getopts urdga:-: OPT; do +while getopts urdgah:-: OPT; do # shellcheck disable=SC2154 if [ "$OPT" = "-" ]; then # long option: reformulate OPT and OPTARG OPT="${OPTARG%%=*}" # extract long option name @@ -35,9 +45,11 @@ while getopts urdga:-: OPT; do g | groups) resources+=(groups) ;; - a | artifactory-url) + h | host) host="${OPTARG}" - grep -qE 'http(s)?://.*' <<<"${host}" || (echo "malformed url name: ${host}. must be of the form http(s)?://.*" && exit 1) + ;; + a | all) + resources=(users repos groups) ;; ??*) usage @@ -69,20 +81,23 @@ function assert_netrc { echo "${location}" } +function toHost { + local host="${1:?You must supply a host}" + host="${host/https:\/\//}" + echo "${host/http:\/\//}" +} function hasNetRcEntry { local h="${1:?No host supplied}" - - h="${h/https:\/\//}" - h="${h/http:\/\//}" - grep -qE "machine[ ]+${h}" "$(assert_netrc)" + grep -qE "machine[ ]+${h}" "$(toHost "${h}")" } function write_netrc { local host="${1:?You must supply a host name}" local netrc netrc=$(assert_netrc) - read -r -p "please enter the username for ${host}: " username - read -rs -p "enter the api token (will not be echoed): " token + host=$(toHost "${host}") + read -r -p "Please enter the username for ${host}: " username + read -rs -p "Enter the api token (will not be echoed): " token # append only to both files cat <<-EOF >> "${netrc}" machine ${host} @@ -94,71 +109,73 @@ function write_netrc { # if they have no netrc file at all, create the file and add an entry if ! hasNetRcEntry "${host}" ; then - echo "added entry to $(write_netrc "${host}" ) needed for curl" >&2 + cat <<-EOF >&2 + + added entry + to $(netrc_location) + for $(write_netrc "${host}" )" + EOF fi function repos { - # jq '.resources |map({type,name})' terraform.tfstate # make sure to not include anything already in state - # we'll make our internal jq structure match that of the tf state file so we can subtract them easy local host="${1:?You must supply the artifactory host}" # literally, usage of jq is 6x faster than bash/read/etc - # GET "${host}/artifactory/api/repositories" returns {key,type,packageType,url} where - # url) points to the UI for that resource?? - # packageType) is cased ?? - # type) is upcased and in "${host}/artifactory/api/repositories/${key}" it's not AND it's called rclass - local url="${host}/artifactory/api/repositories" - curl -snLf "${url}" | jq -re --arg u "${url}" '.[] | "\($u)/\(.key)"' | - xargs -P 10 curl -snLf | - jq -sre ' - group_by(.packageType == "docker" and .rclass == "local") | - (.[0] | map({ - type: "artifactory_\(.rclass)_\(.packageType)_repository.\(.key)", - name: key - }) - ) + - (.[1] | map({ - type: "artifactory_\(.rclass | ascii_downcase)_\(.packageType | ascii_downcase)_\(.dockerApiVersion | ascii_downcase)_repository.\(.key)", - name: key - }) - ) | .[] | + curl "${host}/artifactory/api/repositories" | + jq -re 'map({ + key, + type: (.type | ascii_downcase), + packageType: (.packageType | ascii_downcase) + }) | + group_by(.packageType == "docker" and .type == "local") | + (.[0] | map({ + type: "artifactory_\(.type)_\(.packageType)_repository.\(.key)", + name: .key + }) + ) + + (.[1] | map({ + type: "artifactory_local_docker_v2_repository.\(.key)", + name: .key + }) + ) | .[] | "import { to = \(.type) id = \"\(.name)\" }"' + } && export -f repos function accessTokens { local host="${1:?You must supply the artifactory host}" return 1 - curl -snLf "${host}/artifactory/api/repositories/artifactory/api/security/token" + curl "${host}/artifactory/api/repositories/artifactory/api/security/token" } function ldapGroups { local host="${1:?You must supply the artifactory host}" return 1 - curl -snLf "${host}/access/api/v1/ldap/groups" + curl "${host}/access/api/v1/ldap/groups" } function apiKeys { local host="${1:?You must supply the artifactory host}" return 1 - curl -snLf "${host}/artifactory/api/security/apiKey" + curl "${host}/artifactory/api/security/apiKey" } function groups { local host="${1:?You must supply the artifactory host}" - curl -snLf "${host}/artifactory/api/security/groups" | + curl "${host}/artifactory/api/security/groups" | jq -re '.[].name | - "import { - to = artifactory_group.\(.) - id = \"\(.)\" +"import { + to = artifactory_group.\(. | ascii_downcase) + id = \"\(.)\" }"' } function certificates { local host="${1:?You must supply the artifactory host}" - curl -snLf "${host}/artifactory/api/system/security/certificates/" | + curl "${host}/artifactory/api/system/security/certificates/" | jq -re '.[] | "import { to = artifactory_certificate.\(.certificateAlias) @@ -170,7 +187,7 @@ function distributionPublicKeys { local host="${1:?You must supply the artifactory host}" # untested return 1 - curl -snLf "${host}/artifactory/api/security/keys/trusted" | + curl "${host}/artifactory/api/security/keys/trusted" | jq -re '.keys[] | " import { to = artifactory_distribution_public_key.\(.alias) @@ -182,7 +199,7 @@ function permissions { # these names have spaces in them local host="${1:?You must supply the artifactory host}" return 1 # untested - curl -snLf "${host}/artifactory/api/v2/security/permissions/" | + curl "${host}/artifactory/api/v2/security/permissions/" | jq -re '.[] | select(.name | startswith("INTERNAL") | not) | " import { to = artifactory_permission_target.\(.name) @@ -192,7 +209,7 @@ import { function keyPairs { local host="${1:?You must supply the artifactory host}" return 1 # untested - curl -snLf "${host}/artifactory/api/security/keypair/" | + curl "${host}/artifactory/api/security/keypair/" | jq -re '.[] | " import { to = artifactory_keypair.\(.pairName) @@ -202,8 +219,11 @@ import { function users { # .name has values in it that artifactory will never accept, like email@. Not sure if in that case it should just be user-$RANDOM local host="${1:?You must supply the artifactory host}" - curl -snLf "${host}/artifactory/api/security/users" | jq -re '.[] | - {user: .name | capture("(?\\w+)@(?\\w+)").user, name}| + curl "${host}/artifactory/api/security/users" | jq -re '.[] | + { + user: .name | capture("(?\\w+)@(?\\w+)").user, + name + }| "import { to = artifactory_user.\(.user) id = \"\(.name)\" @@ -232,9 +252,17 @@ function output { done) EOF } +if ! grep -qE 'http(s)?://.*' <<<"${host}"; then + echo "malformed url name: ${host}. must be of the form http(s)?://.*" + exit 1 +fi # shellcheck disable=SC2046 output "${host}" $(echo "${resources[@]}" | tr ' ' '\n' | sort -u | tr '\n' ' ') -# out="$RANDOM-out" -#terraform plan -generate-config-out generated.tf -out "${out} -parallelism=10 -#terraform apply -parallelism=10 +out="$RANDOM-out" +# everytime I try to automate this, I get 'This character is not used within the language.' - it's generating something funky +echo "please run : + +terraform plan -no-color -generate-config-out generated.tf -out ${out} -parallelism=10 +terraform apply -no-color -parallelism=10 +" >&2 diff --git a/scripts/repodata.jq b/scripts/repodata.jq deleted file mode 100644 index 93fb3870..00000000 --- a/scripts/repodata.jq +++ /dev/null @@ -1,15 +0,0 @@ -group_by(.packageType == "docker" and .rclass == "local") | -(.[0] | map({ - to: "artifactory_\(.rclass)_\(.packageType)_repository.\(.key)", - key - }) -) + -(.[1] | map({ - to: "artifactory_\(.rclass | ascii_downcase)_\(.packageType | ascii_downcase)_\(.dockerApiVersion | ascii_downcase)_repository.\(.key)", - key - }) -) | .[] | -"import { - to = \(.to) - id = \"\(.key)\" -}" From bd61ddb01512c5966fc84d5d7cef81085167b0e0 Mon Sep 17 00:00:00 2001 From: Christian Bongiorno Date: Mon, 2 Oct 2023 16:17:44 -0700 Subject: [PATCH 05/10] remove cruft --- .space/devfile.yaml | 13 ------------- 1 file changed, 13 deletions(-) delete mode 100644 .space/devfile.yaml diff --git a/.space/devfile.yaml b/.space/devfile.yaml deleted file mode 100644 index 62bfc1f2..00000000 --- a/.space/devfile.yaml +++ /dev/null @@ -1,13 +0,0 @@ -schemaVersion: 2.2.0 -attributes: - space: - instanceType: large - editor: - type: GoLand -components: - - name: image-build - image: - # (Required) - imageName: jfrog:latest - dockerfile: - uri: . From 126cf7d93c104311d82a6be6e349e9e58f43a95f Mon Sep 17 00:00:00 2001 From: Christian Bongiorno Date: Mon, 2 Oct 2023 16:34:41 -0700 Subject: [PATCH 06/10] GH-621 - Jobs done. No support no docker v2. Docker file for containering this script isn't done, but it's also out-of-scope --- Dockerfile | 8 +++++--- scripts/bulkimport.sh | 2 +- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/Dockerfile b/Dockerfile index bb77f5b6..5b979929 100644 --- a/Dockerfile +++ b/Dockerfile @@ -27,9 +27,11 @@ WORKDIR /home/jfrog COPY --from=builder /src/terraform-provider-artifactory/v5-v6-migrator/tf-v5-migrator /home/jfrog/tf-v5-migrator FROM alpine as importer -RUN apk add --no-cache jq curl bash +RUN apk add --no-cache jq curl bash terraform RUN adduser -S jfrog WORKDIR /home/jfrog +COPY scripts/bulkimport.sh /home/jfrog +RUN chown -R jfrog /home/jfrog USER jfrog -COPY scripts/bulkimport.sh . -ENTRYPOINT ./bulkimport.sh +ENTRYPOINT bash +CMD /home/jfrog/bulkimport.sh diff --git a/scripts/bulkimport.sh b/scripts/bulkimport.sh index 95ee4170..3e56ff10 100755 --- a/scripts/bulkimport.sh +++ b/scripts/bulkimport.sh @@ -108,7 +108,7 @@ function write_netrc { } # if they have no netrc file at all, create the file and add an entry -if ! hasNetRcEntry "${host}" ; then +if ! hasNetRcEntry "${host:?Host not defined}" ; then cat <<-EOF >&2 added entry From 587b1f7d739e15888bacb0470ced2b6f8aac8b89 Mon Sep 17 00:00:00 2001 From: Christian Bongiorno Date: Wed, 4 Oct 2023 11:07:52 -0700 Subject: [PATCH 07/10] Should now work with V1 docker repos, but I have no way to test. Discovered a bug in the netrc logic that needs fixing (removing for now). --- scripts/bulkimport.sh | 88 +++++++++++++++++++++++++++++-------------- 1 file changed, 60 insertions(+), 28 deletions(-) diff --git a/scripts/bulkimport.sh b/scripts/bulkimport.sh index 3e56ff10..059be6dc 100755 --- a/scripts/bulkimport.sh +++ b/scripts/bulkimport.sh @@ -88,7 +88,7 @@ function toHost { } function hasNetRcEntry { local h="${1:?No host supplied}" - grep -qE "machine[ ]+${h}" "$(toHost "${h}")" + grep -qE "machine[ ]+$(toHost "${h}")" <<< "$(netrc_location)" } function write_netrc { @@ -108,41 +108,72 @@ function write_netrc { } # if they have no netrc file at all, create the file and add an entry -if ! hasNetRcEntry "${host:?Host not defined}" ; then - cat <<-EOF >&2 - - added entry - to $(netrc_location) - for $(write_netrc "${host}" )" - EOF -fi +#if ! grep -qE "machine[ ]+$(toHost "${h}")" <<< "$(netrc_location)" ; then +# cat <<-EOF >&2 +# +# added entry +# to $(netrc_location) +# for $(write_netrc "${host}" )" +# EOF +#fi function repos { + # jq '.resources |map({type,name})' terraform.tfstate # make sure to not include anything already in state + # we'll make our internal jq structure match that of the tf state file so we can subtract them easy local host="${1:?You must supply the artifactory host}" # literally, usage of jq is 6x faster than bash/read/etc - curl "${host}/artifactory/api/repositories" | - jq -re 'map({ - key, - type: (.type | ascii_downcase), - packageType: (.packageType | ascii_downcase) - }) | - group_by(.packageType == "docker" and .type == "local") | - (.[0] | map({ - type: "artifactory_\(.type)_\(.packageType)_repository.\(.key)", - name: .key - }) - ) + - (.[1] | map({ - type: "artifactory_local_docker_v2_repository.\(.key)", - name: .key - }) - ) | .[] | + # GET "${host}/artifactory/api/repositories" returns {key,type,packageType,url} where + # url) points to the UI for that resource?? + # packageType) is cased ?? + # type) is upcased and in "${host}/artifactory/api/repositories/${key}" it's not AND it's called rclass + local tempJson + tempJson="$(mktemp)-$RANDOM" + local url="${host}/artifactory/api/repositories" + # we have to sort out the wheat from the chaffee. We're normalizing the input while we sort it out + # and we choose to map to 'rclass' from '.type' because that's how the Go code maps it + curl -snLf "${url}" | + jq 'map({ + key, + rclass: (.type | ascii_downcase), + packageType : (.packageType | ascii_downcase) + }) | + group_by(.packageType == "docker" and .rclass == "local") | + { + safe: .[0], + docker_remap: .[1] + } + ' > "${tempJson}" + + # the URL that comes in the original payload refers to the UI endpoint. Dumb + jq -re --arg u "${url}" '.docker_remap[] | "\($u)/\(.key)"' "${tempJson}" | + #grab the docker-local repos. Curl when used this xargs doesn't seem to be picking up the alias + xargs -n 10 -P 10 curl -snLf | tee onlydocker.json | + # this was literally the only field we couldn't get from before and, apparently it's no longer possible to + # even set docker V1 in RT (even though there is a check, you get an error if you try). But for legacy reason, we + # have to go fetch them. This would be 1 line to simply fetch all repo data and remap it. But SOMEONE is worried about + # scalability + jq -sre 'map(.dockerApiVersion |= ascii_downcase)' | + # combined step 1 with the tf state and step 3, and give them saner names. But what if they have no tf file?? + cat "${tempJson}" - | jq -sre ' + { + safe: .[0].safe, + docker:.[1:][0] + } | + ((.safe | map({ + type: "artifactory_\(.rclass)_\(.packageType)_repository.\(.key | ascii_downcase)", + name: .key + }) + ) + + (.docker | map({ + type: "artifactory_\(.rclass)_\(.packageType)_\(.dockerApiVersion)_repository.\(.key)", + name: .key + }) + )) | .[] | "import { to = \(.type) id = \"\(.name)\" }"' - -} && export -f repos +} function accessTokens { local host="${1:?You must supply the artifactory host}" @@ -231,6 +262,7 @@ function users { } + function output { local host="${1:?You must supply artifactory host name}" # don't touch this heredoc if you want proper output format From 38180827c50e705a1ce922091f994b3d90ffefc3 Mon Sep 17 00:00:00 2001 From: Christian Bongiorno Date: Wed, 4 Oct 2023 16:21:21 -0700 Subject: [PATCH 08/10] add a format function for later use --- scripts/bulkimport.sh | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/scripts/bulkimport.sh b/scripts/bulkimport.sh index 059be6dc..51bf9637 100755 --- a/scripts/bulkimport.sh +++ b/scripts/bulkimport.sh @@ -260,7 +260,17 @@ function users { id = \"\(.name)\" }"' } - +function format { + local resourceType="${1:?You must supply a resource type}" + local key="${2:?You must supply a key}" + local alias="${3:-${key}}" + cat <<-EOF + import { + to = $resourceType.$alias + id = "$key" + } + EOF +} function output { From 4083ecd82f66dd6b806595ec33f07dd3d7f597e4 Mon Sep 17 00:00:00 2001 From: Christian Bongiorno Date: Thu, 5 Oct 2023 00:32:29 +0000 Subject: [PATCH 09/10] Everything works again, and it now includes support for v1 and v2 docker --- sample.tf | 2 +- scripts/bulkimport.sh | 37 ++++++++++++++++++++++--------------- 2 files changed, 23 insertions(+), 16 deletions(-) diff --git a/sample.tf b/sample.tf index 31c2c86e..e8561148 100644 --- a/sample.tf +++ b/sample.tf @@ -3,7 +3,7 @@ terraform { required_providers { artifactory = { source = "registry.terraform.io/jfrog/artifactory" - version = "8.9.2" + version = "9.0.1" } } } diff --git a/scripts/bulkimport.sh b/scripts/bulkimport.sh index 51bf9637..9de37f33 100755 --- a/scripts/bulkimport.sh +++ b/scripts/bulkimport.sh @@ -1,7 +1,7 @@ #!/usr/bin/env bash -${DEBUG:+set -x} +${DEBUG:+set -xv} set -e -set -o errexit -o pipefail -o noclobber -o nounset +set -o errexit -o pipefail -o noclobber shopt -s expand_aliases # shellcheck disable=SC2139 @@ -74,6 +74,7 @@ function netrc_location { ;; esac } + function assert_netrc { local location location="$(netrc_location)" @@ -86,9 +87,10 @@ function toHost { host="${host/https:\/\//}" echo "${host/http:\/\//}" } + function hasNetRcEntry { local h="${1:?No host supplied}" - grep -qE "machine[ ]+$(toHost "${h}")" <<< "$(netrc_location)" + grep -qE "machine[ ]+$(toHost "${h}")" "$(assert_netrc)" } function write_netrc { @@ -107,15 +109,24 @@ function write_netrc { echo "${host}" } +if ! grep -qE 'http(s)?://.*' <<<"${host:-}"; then + echo "malformed url name: '${host}'. must be of the form http(s)?://.*" + exit 1 +fi + # if they have no netrc file at all, create the file and add an entry -#if ! grep -qE "machine[ ]+$(toHost "${h}")" <<< "$(netrc_location)" ; then -# cat <<-EOF >&2 -# -# added entry -# to $(netrc_location) -# for $(write_netrc "${host}" )" -# EOF -#fi +if ! hasNetRcEntry "${host}" ; then + cat <<-EOF >&2 + + added entry + to $(netrc_location) + for $(write_netrc "${host}" )" + EOF +fi + +function get_tf_state { + jq -re '.resources |map({type,name})' terraform.tfstate +} function repos { # jq '.resources |map({type,name})' terraform.tfstate # make sure to not include anything already in state @@ -294,10 +305,6 @@ function output { done) EOF } -if ! grep -qE 'http(s)?://.*' <<<"${host}"; then - echo "malformed url name: ${host}. must be of the form http(s)?://.*" - exit 1 -fi # shellcheck disable=SC2046 output "${host}" $(echo "${resources[@]}" | tr ' ' '\n' | sort -u | tr '\n' ' ') From 51824222a3cda55e0e899a553296b63d47240d6a Mon Sep 17 00:00:00 2001 From: Christian Bongiorno Date: Mon, 9 Oct 2023 17:40:58 +0000 Subject: [PATCH 10/10] GH-621 - remove some debug code --- scripts/bulkimport.sh | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/scripts/bulkimport.sh b/scripts/bulkimport.sh index 9de37f33..1f798a56 100755 --- a/scripts/bulkimport.sh +++ b/scripts/bulkimport.sh @@ -158,11 +158,10 @@ function repos { # the URL that comes in the original payload refers to the UI endpoint. Dumb jq -re --arg u "${url}" '.docker_remap[] | "\($u)/\(.key)"' "${tempJson}" | #grab the docker-local repos. Curl when used this xargs doesn't seem to be picking up the alias - xargs -n 10 -P 10 curl -snLf | tee onlydocker.json | + xargs -n 10 -P 10 curl -snLf | # this was literally the only field we couldn't get from before and, apparently it's no longer possible to # even set docker V1 in RT (even though there is a check, you get an error if you try). But for legacy reason, we - # have to go fetch them. This would be 1 line to simply fetch all repo data and remap it. But SOMEONE is worried about - # scalability + # have to go fetch them. jq -sre 'map(.dockerApiVersion |= ascii_downcase)' | # combined step 1 with the tf state and step 3, and give them saner names. But what if they have no tf file?? cat "${tempJson}" - | jq -sre '