Skip to content

Instantly share code, notes, and snippets.

@typebrook
Last active February 25, 2023 11:32
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save typebrook/d166d5e8d0a293c30f697b0f403b3c0e to your computer and use it in GitHub Desktop.
Save typebrook/d166d5e8d0a293c30f697b0f403b3c0e to your computer and use it in GitHub Desktop.
A simple script to upload OsmChange file #osm #changeset #script

Upload OsmChange file with script

Usage

# By default, changeset would be uploaded to test server: 
# https://master.apis.dev.openstreetmap.org
./osm.api.changeset.commit <OSC FILE>

# Use --serious to upload changeset to normal OSM server
./osm.api.changeset.commit --serious <OSC FILE>

# STDIN would be treated as comment, or a prompt shows up
echo "This is changeset comment" | ./osm.api.changeset.commit <OSC FILE>

# Set env OSM_USER_PASSWD as the following format, to skip prompt input:
# <USER>:<PASSWORD>
OSM_USER_PASSWD=<USER>:<PASSWORD> osm.api.changeset.commit <OSC FILE>

# Set env CHANGESET with the id of a open changeset
# Then script will not create a new changeset
# Instead, use existing one to upload OSC file and close it
CHANGESET=<ID_OF_CHANGESET> ./osm.api.changeset.commit <OSC FILE>

# Alternative way with online script
curl -fsS https://raw.githubusercontent.com/typebrook/helper/dev/bin/osm/osm.api.changeset.commit | \
bash /dev/stdin <OSC FILE>

# For development, use bats for testing:
# https://bats-core.readthedocs.io/
./test.bats
#!/bin/sh
set -e
OSM_SERVER=https://api.openstreetmap.org
OSM_TEST_SERVER=https://master.apis.dev.openstreetmap.org
# Commit to test server unless --serious is given
if echo "$@" | grep -q '\--serious' ; then
SERVER=$OSM_SERVER
else
SERVER=$OSM_TEST_SERVER
fi
OSM_API=${SERVER}/api/0.6
FILE="$(echo "$@" | sed s/--serious//)"
# Fail if no OSC file is given
if [ -z "$FILE" ]; then
echo No OSC file is given >&2
exit 1
fi
# Prompt for comment and User:Password
if [ ! -t 0 ]; then
comment=$(cat)
else
echo -n 'Type comment: '
read -r comment </dev/tty
fi
if [ -z "${OSM_USER_PASSWD}" ]; then
echo -n 'Type USER:PASSWD: '
read -r OSM_USER_PASSWD </dev/tty
fi
# API call for changeset create
# If env SOURCE is set, add it as part of changeset:
# <tag k='source' v='$SOURCE'/>
create_changeset() {
SOURCE_TAG="${SOURCE:+$(printf "<tag k='source' v='%s'/>" $SOURCE)}"
# Create changeset with given information
response="$(curl ${OSM_API}/changeset/create \
--fail-with-body \
--user "$OSM_USER_PASSWD" \
--upload-file - \
--silent \
<<' EOF'
<osm>
<changeset>
${SOURCE_TAG}
<tag k='comment' v='${comment}'/>
<tag k='created_by' v='bash script'/>
<tag k='bot' v='yes'/>
</changeset>
</osm>
EOF
)" || local result=fail
# If return code >200, or the response is not a sequence number
# Exit with return code 1
if [ "$result" = fail ] || ! echo "$response" | grep -qE '^[[:digit:]]+$' ; then
echo
echo Fail to create a new changeset: >&2
echo "$response" >&2
return 1
else
changeset_id=$response
fi
}
# API call for uploading OSC file
uploade_file_to_changeset() {
response="$(curl -X POST $OSM_API/changeset/$1/upload \
--fail-with-body \
--user "$OSM_USER_PASSWD" \
--upload-file - \
--silent
)" || local result=fail
if [ "$result" = fail ]; then
echo
echo Fail to upload OSC file: >&2
echo "$response" >&2
return 1
fi
}
# API call for closing changeset
close_changeset() {
response="$(curl -X PUT ${OSM_API}/changeset/$1/close \
--fail-with-body \
--user "$OSM_USER_PASSWD" \
--silent
)" || local result=fail
if [ "$result" = fail ]; then
echo
echo Fail to close changeset: >&2
echo "$response" >&2
return 1
fi
}
# Create changeset when CHANGESET is not set
if [ -z $CHANGESET ]; then
echo 'CHANGESET is not set, create a new one'
create_changeset && \
echo "Changeset created, check ${SERVER}/changeset/${changeset_id}" || \
exit 1
else
echo "CHANGESET is set, use ${CHANGESET} as changeset ID"
changeset_id=$CHANGESET
fi
# Upload OSC file to Changeset
sed -Ee "/<(node|way|relation)/ s/>/ changeset=\"${changeset_id}\">/" $FILE |\
uploade_file_to_changeset ${changeset_id} && \
echo Upload file $FILE to changeset ${changeset_id} || \
exit 1
# Close Changeset
close_changeset ${changeset_id} && \
echo Changeset ${changeset_id} closed || \
exit 1
#! /bin/bats --verbose-run
export OSM_USER_PASSWD=
setup() {
# Create OSC file for each test
cat <<' OSC' >test.osc
<osmChange version="0.6" generator="bash script">
<create>
<node id="-1" version="1" lat="24" lon="121">
</node>
</create>
</osmChange>
OSC
}
teardown() {
rm test.osc
}
check_passwd() {
if [[ -z $OSM_USER_PASSWD ]]; then
skip 'Please set env OSM_USER_PASSWD in .bats file'
fi
}
print_oputput() {
echo -e "${output}" | sed 's/^/\t/' >&3
}
@test "POSIX compatible" {
run checkbashisms -f ./osm.api.changeset.commit
[ "$status" -eq 0 ]
print_oputput
}
@test "Fail with zero augument" {
run ./osm.api.changeset.commit
[ "$status" -eq 1 ]
[[ "$output" =~ 'No OSC file is given' ]]
print_oputput
}
@test "Fail with invalid user/passwd" {
run sh -c 'echo "test comment" | OSM_USER_PASSWD=foo:bar ./osm.api.changeset.commit test.osc'
[ "$status" -eq 1 ]
[[ "$output" =~ 'Fail to create a new changeset:' ]]
print_oputput
}
@test "Fail with invalid OSC file" {
check_passwd
run sh -c 'echo "test comment" | ./osm.api.changeset.commit <(echo "Some random file content")'
[[ "$output" =~ 'Changeset created' ]]
[[ "$output" =~ 'Fail to upload OSC file:' ]]
[ "$status" -eq 1 ]
print_oputput
}
@test "Create changeset" {
check_passwd
run sh -c 'echo "test comment" | ./osm.api.changeset.commit test.osc'
[[ "$output" =~ 'Changeset created' ]]
[ "$status" -eq 0 ]
print_oputput
}
@dy1owens
Copy link

dy1owens commented Jun 10, 2022

My Bash Version:

GNU bash, version 5.1.16(1)-release (x86_64-apple-darwin21.1.0)
Copyright (C) 2020 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later http://gnu.org/licenses/gpl.html

But Still I am getting this error:

_line 4: shopt: lastpipe: invalid shell option name
logout

Saving session...
...copying shared history...
...saving history...truncating history files...
...completed.

[Process completed]_

@typebrook
Copy link
Author

typebrook commented Jun 11, 2022

@dy1owens

GNU bash, version 5.1.16
_line 4: shopt: lastpipe: invalid shell option name

Weird, lastpipe should be valid on v5
Maybe your /bin/bash points to an old version?

Though I am not Mac user, but because by default MacOS use bash v3. So I guess you use homebrew to get bash v5, and maybe it locates at /usr/local/bin

Please try running /bin/bash --version for more details.

@jidanni
Copy link

jidanni commented Feb 24, 2023

Wow, even works offline :-(
Says changeset created, despite no Internet.

$ ./osm.api.changeset.commit
Type comment: dsds
Type USER:PASSWD: dsds:dsds
Changeset created, check https://master.apis.dev.openstreetmap.org/changeset/

Ah, this pipe blocks -e acting on the failure:

<<EOF | tail -1

@typebrook
Copy link
Author

typebrook commented Feb 24, 2023

@jidanni
Nice catch!

Ah, this pipe blocks -e acting on the failure:

I guess set -o pipefail should be added.

I didn't write too many fail-proof logic for API calls. I will improve this then leave some comments here after it is done.

@jidanni
Copy link

jidanni commented Feb 24, 2023

I think you're going in the wrong direction. I think you should just make sure the darn thing is compatible with the Bourne shell, And not go off the deep end with the BASH stuff. It's not that hard to just write it to a file and then you'll won't mess up the error code detection. Then you can always read from the file later on.

Maybe even ID=$(curl ....) would be OK.

@typebrook
Copy link
Author

typebrook commented Feb 24, 2023

Well, http status codes and return value of curl are different things. So correctly handle it is necessary.
My new commit resolve this by explicitly check http status code is 200.

not go off the deep end with the BASH

Since BASH has many convenient features. I only use Bourne shell in some edge cases. POSIX is fine, but I believe executable binary is more portable than POSIX XD

@jidanni
Copy link

jidanni commented Feb 24, 2023

Okay. Then never mind the 'checkbashisms' script I was going to tell you about. Also curl has some --fail options you might take advantage of instead of examining the code manually.
As to the the /dev/tty stuff and -t ... just more asking for trouble.
case ... [0123456789]*[0123456789] esac could even check for valid changeset numbers... I'll leave the challenge of how to even check for one digit changeset numbers .... Anyway you would have the valid matches separated by a pipe and then you fail if it doesn't match any of them. I'm sorry I'm writing this on a cell phone otherwise I'd go in to more detail. Anyway even a shell from the '70s could work with that. I'm just saying like you see in the other user's comment... You never know what kind of shell simebody might have... it might even be a zsh. So there's no point making the whole script break. It's (or was) just such a short script. It should be just like one of those Debian system scripts that don't take any chances with the kid stuff.

@typebrook
Copy link
Author

typebrook commented Feb 25, 2023

Then never mind the 'checkbashisms' script I was going to tell you about

Good to know this! I think I have heard this thing, now it is in my filesystem.

Also curl has some --fail options

This is just for case of no output, I think you mean --fail-with-body (Fail when status code >400)?
I think this is a good approach.

As to the the /dev/tty stuff and -t ... just more asking for trouble.

I prefer the workflow looks like sending a mail. User can make comment both by STDIN or terminal.

case ... [0123456789]*[0123456789] esac could even check for valid changeset numbers... I'll leave the challenge of how to even check for one digit changeset numbers

I use pattern check of bash in [[ ]] only because it is just there. If I need to do this in Bourne shell, I prefer grep.

I'm just saying like you see in the other user's comment... You never know what kind of shell simebody might have... it might even be a zsh.

Since shebang is marked as /bin/bash, I don't think it is a problem. @dy1owens 's issue is using bash v3. Check variable BASH_VERSION in script should be the solution.

It's (or was) just such a short script. It should be just like one of those Debian system scripts that don't take any chances with the kid stuff.

Okay...let me try if I can make it POSIX compatible. Should be a good practice for me.

@typebrook
Copy link
Author

@jidanni I think now it is POSIX compatible, bats is introduced for testing.

Screenshot_2023-02-25-16-25-26_1920x1080

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment