Informatie ophalen via API

De Internet wereld hangt al geruime tijd flink aan elkaar met API’s. Een API is een “Application Programming Interface”. Daar kan je dus veel dingen mee.

Zoals een klein scriptje dat ik gemaakt heb voor wat basis statistieken omtrent Crypto currency. Ik heb een aantal coins van, jammergenoeg, lage waarde maar toch wil ik niet elke keer zelf opzoeken wat de huidige prijs verloop is. Ook had ik eerst een App op de mobiel maar na een update van de leverancier was mijn overzicht weer leeg. Fijn.

Coincapmarket.com is een hele goede website om snel inzicht te krijgen in het verloop, de prijs en nog meer informatie omtrent een coin. Blijkbaar ook, via een eenmalige registratie, een API functionaliteit. Hè presto.

Een API call is niets meer dan “iets” opvragen en dat krijg je, vaak, terug als JSON data. Deze structuur is prima te verwerken. Omgekeerd kan ook, via de juiste structuur data aanleveren aan het systeem. Hiermee is dus veel automatisering mogelijk tussen verschillende systemen.

#
# Created 9-Feb 2019
# @author: bartjan@pc-mania.nl
# https://github.com/barreljan/cc-stats
# 
import requests
import sys
import smtplib

assets = {
    'BTC': 2,
    'ETH': 1,
    'MNR': 10
          }


def sendmail(msg):
    msg = "Subject: Latest coin prices" + "\n\n" + msg
    server = smtplib.SMTP('smtp.somehost.com', 25)
    server.sendmail('noreply@yourdomain.org', 'john@doe.net', msg)


def cryptodata(assets):
    coins = str()
    for coin, qty in assets.items():
        if coins is "":
            coins += coin
        else:
            coins += ",{}".format(coin)

    link = 'https://pro-api.coinmarketcap.com/v1/cryptocurrency/quotes/latest?symbol={}'.format(coins)
    api_key = 'your Coinmarketcap Pro key'
    headers = {
        'X-CMC_PRO_API_KEY': api_key,
        'Accept': 'application/json'
    }

    try:
        data = requests.get(link, headers=headers)
    except Exception:
        print("Could not connect")
        sys.exit(1)

    jdata = data.json()

    try:
        if jdata['data']:
            return jdata['data']
        elif not jdata['data']:
            raise KeyError('No data returned')
    except KeyError:
        print("API Call went wrong, no usable data returned")
        sys.exit(1)


# Run it
allcrypto = cryptodata(assets)

msg = "{0:8}{1:<8}\t{2}\t{3}\n".format("Coin", "Qty", "Price", "Totals (USD)")

for coin, coinitems in allcrypto.items():
    price = coinitems['quote']['USD']['price']
    assetval = price*assets[coin]
    msg += "{0:8}{1:<8}\t{2:.2f}:\t{3:.2f}\n".format(coin, assets[coin], price, assetval)

sendmail(msg)

En zo kan je vrij eenvoudig data ophalen en verwerken. Dit script doet namelijk:

  • alle informatie ophalen, op basis van de quote (dus pricing) op basis van de gewenste coins (assets)
    • code: allcrypto = cryptodata(assets)
  • een bericht starten
  • per coin uit de json data de prijs halen en de totale waarde berekenen
    • code: for coin, coinitems in allcrypto.items()
  • het bericht aanvullen
  • en uiteindelijk het bericht versturen
    • code: sendmail(msg)

En uiteindelijk krijg je een mooi overzicht in de email. Dit script draait nu elke dag op 12.00u en stuurt een email naar mijn postvak.

Coin    Qty     Price       Totals (USD)
BTC     2       3651.62:    7303.24
ETH     1       117.69 :    117.69

Uiteraard is dit script ook via Github te vinden. Zie het aan de linkerzijde de Link.

ps.: de coins genoemd hier, zijn als voorbeeld en niet mijn jammerlijke hoeveelheid alt-coins.

Automatisch lichten aanzetten met behulp van een API

Ooit, een hele tijd terug heb ik een APC rack pdu (ap7921) in huis gehaald om 2 dingen te doen. Het meten van het totale verbruik van de mediahoek én het in/uitschakelen van lichten of andere zaken. Dat kan prima met deze pdu, want er zit netwerk op en dus ook via SNMP aan te sturen. Via een php webpagina (die ik met mijn mobile device kan openen) kan ik al heel lang de belangrijkste outlets schakelen en ik heb ook al een tijd een cronjob op een server draaien die de lichten schakelen. Ja, ook de kerstboom hoewel het stekkerblok op die outlet nu onder de bank ligt voor algemeen gebruik. De subwoofer was voor het gemak, tijdens een film licht uit, sub aan en geniet.

De pagina stuurt in feite ook via een ‘snmpset’ commando een 1 of 2 naar de juiste poort. Daarbij is de kleur groen ‘aan’ en rood is ‘uit’ puur voor de opmaak.

Het script in cronjob is een paar keer herschreven en kwijt geraakt door een crash van de OrangePi thuis en dus weer opnieuw gemaakt. Voor nu is het een ‘fancy’ versie want het kan uiteraard ook gewoon met praktisch 8 of zo regels. Maar dat is het mooie, je wil leren en je script verder ontwikkelen, verharden. Een geval van “good practice” dus.

#!/bin/bash#
# Switching on or off light at home
# bartjan@pc-mania.nl - 08 Mar 2018

snmp_exec="/usr/bin/snmpset"

function check_reqs {
        if [ ! -f $snmp_exec ]; then
                echo "Error: snmpset executable not found.."
                echo ""
                exit 1
        fi
}

function show_help {
        echo "Error: incorrect syntax.."
        echo "Usage: ./lights.sh [on|off]"
        echo ""
}

function switch {
        # 1st arg is outlet nr (integer)
        # 2nd arg options: on / off
        if [ $2 == "on" ]; then
                ${snmp_exec} -v2c -cpublic 192.168.1.251 1.3.6.1.4.1.318.1.1.4.4.2.1.3.${1} i 1 1>/dev/null 2>&1
        elif [ $2 == "off" ]; then
                ${snmp_exec} -v2c -cpublic 192.168.1.251 1.3.6.1.4.1.318.1.1.4.4.2.1.3.${1} i 2 1>/dev/null 2>&1
fi
}

# Start the program

check_reqs

if [ ! $1 ]; then
        show_help
        exit 1
fi


if [ $1 == "on" ]; then
        echo "Iluminate"
        switch 1 on
        switch 8 on
elif [ $1 == "off" ]; then
        echo "Deluminate"
        switch 1 off
        switch 8 off
else
        show_help
        exit 1
fi

De output op de cli is vanzelf sprekend. De cronjob zelf voert de output naar dev/null.

Nu kwam het zo naar boven dat in de herfst de tijden van de job in cron verschuiven. Elke keer dit aanpassen is suf en dat kan slimmer. Ik ben al een ruime tijd veel meer met Python bezig en dat bevalt erg goed. Ik heb een script geschreven die tijden ophaalt via een API call bij een publieke dienst aan de hand van de fysieke locatie. Hiermee kan ik de crontab van de user ‘root’ op de OrangePi weer aanpassen. Deze gebruikt deze dus de handige module ‘python-crontab‘.

#!/usr/bin/python
#
# @author bartjan@pc-mania.nl
# Oct-6,2018

import requests
import json
import smtplib
import sys
import time
from crontab import CronTab

link = 'https://api.sunrise-sunset.org/json?lat=51.840449&lng=-4.972762?&formatted=0'
localtime = time.localtime()

def time_of(input):
    # Get the JSON output from the API
    try:
        data = requests.get(link).text
    except Exception:
        sendmail("Failed to connect to API")
        sys.exit()
    data = json.loads(data)

    # Fetch the wanted time
    try:
        time = str(data['results'][input]).split('T')[1].split('+')[0]
    except Exception:
        sendmail("No usable data is returned, keeping old time")
        sys.exit()

    return time

def reset_crontab(minute,hour):
    cron = CronTab(user='root')

    # Check and remove the old job
    try:
        for job in cron.find_command('lights.sh on'):
            job.delete()
    except Exception:
        sendmail("Old cron job not found, stopping update")
        sys.exit()

    # Set the new job at the given time
    job = cron.new(command='/bin/bash /root/scripts/lights.sh on >/dev/null 2>&1')
    job.minute.on(minute)
    job.hour.on(hour)

    # Save job
    try:
        cron.write()
    except Exception:
        sendmail("Writing new cron failed")
        sys.exit()

def sendmail(msg):
    msg = "Subject: sunset tool warning" + "\n\n" + msg
    server = smtplib.SMTP('smtp.yourisp.org',25)
    server.sendmail('you@domain.net','johndoe@somedomain.com',msg)

###

# Get new time and split in hour/minute
time = time_of('nautical_twilight_end')
hour = time.split(':')[0]
if not localtime.tm_isdst:
    # Correct daylight savings
    hour = hour - 1
minute = time.split(':')[1]

# Update (so reset) the cronjob
reset_crontab(minute,hour)

Dit script draait sowieso ook in de crontab van dezelfde user ‘root’. Dit alleen 1x per dag om 06.00, om te job voor diezelfde dag goed te zetten. De gewenste tijd is aan te passen door de parameter “time” te veranderen. De API call geeft namelijk al deze velden terug (formatted):

{
"results":
    {
        "sunrise":"6:30:51 AM",
        "sunset":"5:44:52 PM",
        "solar_noon":"12:07:51 PM",
        "day_length":"11:14:01",
        "civil_twilight_begin":"5:56:38 AM",
        "civil_twilight_end":"6:19:05 PM",
        "nautical_twilight_begin":"5:16:49 AM",
        "nautical_twilight_end":"6:58:54 PM",
        "astronomical_twilight_begin":"4:36:17 AM",
        "astronomical_twilight_end":"7:39:26 PM"
    },
    "status":"OK"
}

Of zonder format, de toevoeging ?&formatted=0 in de url zoals ik nu gebruik (scheelt een extra stapel stappen om naar 24-uurs tijd om te zetten):

{
"results":
    {
        "sunrise":"2018-10-08T06:34:22+00:00",
        "sunset":"2018-10-08T17:40:13+00:00",
        "solar_noon":"2018-10-08T12:07:17+00:00",
        "day_length":39951,
        "civil_twilight_begin":"2018-10-08T06:00:05+00:00",
        "civil_twilight_end":"2018-10-08T18:14:30+00:00",
        "nautical_twilight_begin":"2018-10-08T05:20:18+00:00",
        "nautical_twilight_end":"2018-10-08T18:54:17+00:00",
        "astronomical_twilight_begin":"2018-10-08T04:39:57+00:00",
        "astronomical_twilight_end":"2018-10-08T19:34:38+00:00"
    },
"status":"OK"
}

Dus, nu past de cronjob zichzelf aan en zorgt dat de 2 lichten rondom het tv-meubel aangaan als het begint te schemeren en ik hoef niet meer in te loggen via SSH en crontab -e te doen en de tijd per x-dagen aan te passen.

De ‘nautical_twilight’ is een andere benadering van wanneer het donker word. Lees anders even ook deze pagina voor meer informatie.

Ik zie 2 IPv6 adressen en maar 1 geconfigureerd?

Ja, het kan. Ze zal vast eens zien, dat wanneer je op een Windows machine (in dit geval Windows 2008 R2) een vast IPv6 adres configureert toch nog een 2e adres ziet via een ‘ipconfig’. Nee, dit is niet het adres beginnend met “fe80:: ”

Dit is mede door de configuratie op het netwerk waar de machine op aangesloten zit. Het adres komt van de ‘Router Discovery’ af, welke in een soort DHCP manier van werken prima adressen uit deelt aan clients.

Echter, in een strakke omgeving waar hosting sprake is, is 1 vast adres voor zowel inkomend maar meer ook uitgaand erg praktisch. Dit is uit te zetten:

netsh int ipv6 set int "Local Area Connection" routerdiscovery=disabled

Waarbij ‘Local Area Connection’ uiteraard de juiste naam moet zijn.

Na in het invoeren is het 2e adres ook direct weg.

Cacti 1.0.4 op Centos7 met PHP7 en Percona-server

Als je een server hebt met Centos7 gecombineerd met PHP7.x en dus ook Percona Server 5.x geinstalleerd, dan is het installeren van Cacti wel een beetje een domper.

Want, de vereisten van Cacti op hun website zijn als volgt (belangrijkste items):

  • httpd,php,php-mysql,php-snmp
  • php-xml
  • mysql,mysql-server

Dat zou zeggen, de installatie is zo gedaan. Kwestie van yum install en klaar. Maar helaas;

Error: Package: 1:mariadb-5.5.52-1.el7.x86_64 (base)
           Requires: mariadb-libs(x86-64) = 1:5.5.52-1.el7
           Available: 1:mariadb-libs-5.5.52-1.el7.x86_64 (base)
               mariadb-libs(x86-64) = 1:5.5.52-1.el7
Error: Package: php-ldap-5.4.16-42.el7.x86_64 (base)
           Requires: php-common(x86-64) = 5.4.16-42.el7
           Installed: php-common-7.0.17-1.el7.remi.x86_64 (@remi-php70)
               php-common(x86-64) = 7.0.17-1.el7.remi
           Available: php-common-5.4.16-42.el7.x86_64 (base)
               php-common(x86-64) = 5.4.16-42.el7
Error: Package: php-snmp-5.4.16-42.el7.x86_64 (base)
           Requires: php-common(x86-64) = 5.4.16-42.el7
           Installed: php-common-7.0.17-1.el7.remi.x86_64 (@remi-php70)
               php-common(x86-64) = 7.0.17-1.el7.remi
           Available: php-common-5.4.16-42.el7.x86_64 (base)
               php-common(x86-64) = 5.4.16-42.el7
Error: php-mysql conflicts with php-mysqlnd-7.0.17-1.el7.remi.x86_64
Error: Package: php-mbstring-5.4.16-42.el7.x86_64 (base)
           Requires: php-common(x86-64) = 5.4.16-42.el7
           Installed: php-common-7.0.17-1.el7.remi.x86_64 (@remi-php70)
               php-common(x86-64) = 7.0.17-1.el7.remi
           Available: php-common-5.4.16-42.el7.x86_64 (base)
               php-common(x86-64) = 5.4.16-42.el7
Error: Package: cacti-1.0.4-1.el7.noarch (epel)
           Requires: php-mysql
           Available: php-mysqlnd-5.4.16-42.el7.x86_64 (base)
               php-mysql = 5.4.16-42.el7
           Installed: php-mysqlnd-7.0.17-1.el7.remi.x86_64 (@remi-php70)
               Not found
Error: Package: php-xml-5.4.16-42.el7.x86_64 (base)
           Requires: php-common(x86-64) = 5.4.16-42.el7
           Installed: php-common-7.0.17-1.el7.remi.x86_64 (@remi-php70)
               php-common(x86-64) = 7.0.17-1.el7.remi
           Available: php-common-5.4.16-42.el7.x86_64 (base)
               php-common(x86-64) = 5.4.16-42.el7
Error: Package: php-mysql-5.4.16-42.el7.x86_64 (base)
           Requires: php-pdo(x86-64) = 5.4.16-42.el7
           Installed: php-pdo-7.0.17-1.el7.remi.x86_64 (@remi-php70)
               php-pdo(x86-64) = 7.0.17-1.el7.remi
           Available: php-pdo-5.4.16-42.el7.x86_64 (base)
               php-pdo(x86-64) = 5.4.16-42.el7
 You could try using --skip-broken to work around the problem
 You could try running: rpm -Va --nofiles --nodigest

De optie –enablerepo=remi-php70 geeft wel een deel opgelost terug maar niet alles. Jammer genoeg.

Error: Package: cacti-1.0.4-1.el7.noarch (epel)
           Requires: php-mysql
           Available: php-mysql-5.4.16-42.el7.x86_64 (base)
               php-mysql = 5.4.16-42.el7
           Available: php-mysqlnd-5.4.16-42.el7.x86_64 (base)
               php-mysql = 5.4.16-42.el7
           Available: php-pecl-mysql-1.0.0-0.8.20151007git294ce3b.el7.remi.7.0.x86_64 (remi-php70)
               php-mysql = 1:1.0.0
           Available: php-pecl-mysql-1.0.0-0.9.20151007git294ce3b.el7.remi.7.0.x86_64 (remi-php70)
               php-mysql = 1:1.0.0
           Installed: php-mysqlnd-7.0.17-1.el7.remi.x86_64 (@remi-php70)
               Not found
           Available: php-mysqlnd-7.0.16-1.el7.remi.x86_64 (remi-php70)
               Not found
Error: Package: 1:mariadb-5.5.52-1.el7.x86_64 (base)
           Requires: mariadb-libs(x86-64) = 1:5.5.52-1.el7
           Available: 1:mariadb-libs-5.5.52-1.el7.x86_64 (base)
               mariadb-libs(x86-64) = 1:5.5.52-1.el7
 You could try using --skip-broken to work around the problem
 You could try running: rpm -Va --nofiles --nodigest

De optie –skip-broken geeft helemaal niet het gewenste resultaat. Oftewel we zijn weer terug bij af.

Er is een weg dat we het wel aan de praat krijgen middels de source RPM.

We maken allereerst een extra user aan “rpmbuild” en installeren de package rpmbuild. Onder de user rpmbuild downloaden we de source RPM voor cacti versie 1.0.4 en pakken deze uit:

useradd rpmbuild
yum install -y rpm-build
su - rpmbuild

wget https://dl.fedoraproject.org/pub/epel/7/SRPMS/c/cacti-1.0.4-1.el7.src.rpm

rpm -i cacti-1.0.4-1.el7.src.rpm

Er zijn dan een tweetal directories aangemaakt in de map “rpmbuild”. We open de directory “SPECS” en stellen de originele even veilig waarna we de “cacti.spec” openen met een text editor zoals VIM:cd

cd rpmbuild/

ls -l
total 0
drwxr-xr-x. 2 rpmbuild rpmbuild 124 Mar 31 13:46 SOURCES
drwxr-xr-x. 2 rpmbuild rpmbuild  24 Mar 31 13:46 SPECS

cd SPECS
cp cacti.spec cacti.orig

vim cacti.spec

Op regel nummer 13 en 14 vind je de vereisten php (en componenten) en mariadb terug. Deze kan je uitzetten door een ‘#’ er voor te zetten:

#Requires: php, php-ldap, php-mbstring, php-mysql, php-pdo, php-snmp, php-xml
#Requires: mariadb

Hierna moeten nog 2 regels uitgezet worden door een comment (‘#’) te plaatsen op regel 46 en 71:

#%{__install} -d -m 0755 %{buildroot}/%{_pkgdocdir}
#%{__cp} -a docs/ %{buildroot}/%{_pkgdocdir}

Daarna, zet in de regel 3 iets extra’s neer om een eigen release naam aan te duiden, zodat duidelijk is dat deze niet origineel is:

Release: 1%{?dist}
word:
Release: 1%{?dist}.bj

Nu is alles in place om de RPM weer terug te bouwen:

rpmbuild -ba cacti.spec

Er is zijn nu diverse files gemaakt waarmee je verder kan. Je kan ook de nieuwe Source RPM kopieren om weer extra aanpassingen elders te maken. Dat doen we nu niet, we gaan de gecompileerde RPM installeren. Deze is gemaakt in de directory rpmbuild/RPMS/. Uiteraard moeten we weer uit het user account ‘rpmbuild’ gaan en root access hebben. De installatie kan door middel van:

yum install /home/rpmbuild/rpmbuild/RPMS/noarch/cacti-1.0.4-1.el7.centos.bj.noarch.rpm

En daarmee is Cacti door Yum geinstalleerd op een CentOS7 machine met PHP7.0 en Percona-Server 5.7.

Het beste is dan te starten vanaf stap 2:

http://docs.cacti.net/manual:088:1_installation.1_install_unix.5_install_and_configure_cacti

In mijn geval n.a.v. de nieuwe server is het volgende eerst inregelen, en dan op stap 2 verder:

mv /etc/httpd/conf.d/cacti.conf /etc/httpd/conf.d/cacti.conf.old
ln -s /usr/share/cacti /var/www/cacti

vim /etc/httpd/conf.d/cacti.pc-mania.nl.conf:
<VirtualHost *:80>
 DocumentRoot /var/www/cacti/
 ServerName cacti.pc-mania.nl
 <Directory /var/www/cacti/>
      AllowOverride all
      Options MultiViews IncludesNoExec FollowSymLinks
 </Directory>
</VirtualHost>

<VirtualHost *:443>
 DocumentRoot /var/www/cacti
 ServerName cacti.pc-mania.nl
 SSLEngine on
 SSLCertificateFile /etc/letsencrypt/live/cacti.pc-mania.nl/cert.pem
 SSLCertificateKeyFile /etc/letsencrypt/live/cacti.pc-mania.nl/privkey.pem
 SSLCertificateChainFile /etc/letsencrypt/live/cacti.pc-mania.nl/chain.pem

<Directory /var/www/cacti/>
       <IfModule mod_authz_core.c>
               # httpd 2.4
               Require all granted
       </IfModule>
       <IfModule !mod_authz_core.c>
               # httpd 2.2
               Order allow,deny
               Allow from all
       </IfModule>
      AllowOverride all
      Options MultiViews IncludesNoExec FollowSymLinks
</Directory>

<Directory /var/www/cacti/install>
        # mod_security overrides.
        # Uncomment these if you use mod_security.
        # allow POST of application/x-www-form-urlencoded during install
        #SecRuleRemoveById 960010
        # permit the specification of the rrdtool paths during install
        #SecRuleRemoveById 900011
</Directory>


# These sections marked "Require all denied" (or "Deny from all")
# should not be modified.
# These are in place in order to harden Cacti.
<Directory /var/www/cacti/log>
        <IfModule mod_authz_core.c>
                Require all denied
        </IfModule>
        <IfModule !mod_authz_core.c>
                Order deny,allow
                Deny from all
        </IfModule>
</Directory>
<Directory /var/www/cacti/rra>
        <IfModule mod_authz_core.c>
                Require all denied
        </IfModule>
        <IfModule !mod_authz_core.c>
                Order deny,allow
                Deny from all
        </IfModule>
</Directory>

</VirtualHost>