How to set S3 path style in Clojure using Amazonica library? - amazon-web-services

I'm a Ruby dev moving over to Clojure and I'm having trouble understanding how to translate the following Java call into Clojure based on the conventions used in the Clojure library Amazonica.
AmazonS3 client = new AmazonS3Client(credentials);
client.setS3ClientOptions(new S3ClientOptions().withPathStyleAccess(true));
The code I have at the moment is:
(ns spurious-aws-sdk-helper.core
(:use [amazonica.aws.s3]])
(:require [amazonica.core :refer [ex->map]]))
(def credentials {:access-key "development_access"
:secret-key "development_secret"
:endpoint "s3.spurious.localhost:49154"
:client-config {:protocol "http"}})
(try
(amazonica.aws.s3/set-s3client-options {:path-style-access true})
(create-bucket credentials "testing")
(catch Exception e
(clojure.pprint/write (ex->map e))))
But I'm getting the following error:
com.amazonaws.http.AmazonHttpClient executeHelper
INFO: Unable to execute HTTP request: testing.s3.spurious.localhost
java.net.UnknownHostException: testing.s3.spurious.localhost
This doesn't look to be correct as it's prefixing the bucket name (testing) onto the hostname. Where as I need the SDK to talk to our local (fake) S3 service (s3.spurious.localhost:49154) using path style.
e.g. like http://s3.spurious.localhost:49154/testing
I think it's because I'm not translating the Java code correctly...
(amazonica.aws.s3/set-s3client-options {:path-style-access true})
...this is passing a map to set-s3client-options rather than what it should be, which is passing the result of calling withPathStyleAccess(true) on a new instance of S3ClientOptions. But I don't know how to do that here?
Any help would be greatly appreciated.
UPDATE
Here is the latest version of the code (which still doesn't work)...
(ns spurious-aws-sdk-helper.core
(:use [amazonica.aws.s3])
(:require [amazonica.core :refer [ex->map]]))
(def credentials {:access-key "development_access"
:secret-key "development_secret"
:endpoint "s3.spurious.localhost:49154"
:client-config {:protocol "http"}})
(try
(amazonica.aws.s3/set-s3client-options
(. (com.amazonaws.services.s3.S3ClientOptions.) setPathStyleAccess true))
(create-bucket credentials "testing")
(catch Exception e
(clojure.pprint/write (ex->map e))))

Your first call to set up the S3ClientOptions is the correct one.
I think you're confusing what that setting does.
It changes the pre-signed URL generation from using the Virtual Hosted Style https://my_bucket_name.s3.amazonaws.com/my_key to the Path Style https://s3.amazonaws.com/my_bucket_name/my_key
This is necessary if your bucket name contains dots, as the virtual hosted style breaks Amazon's SSL certificate identification (i.e. it only supports the wildcard *.s3.amazonaws.com)
So if you're generating pre-signed URLs on a bucket name containing dots in Clojure e.g.
(s3/generate-presigned-url bucket key (-> 6 hours from-now)))
You will first need to set the path style using
(amazonica.aws.s3/set-s3client-options {:path-style-access true})

Thanks to #stukennedy for responding to the open question. I should have come back long ago and updated this space with the actual solution which I managed to figure out and implement ~8months ago (Feb 2nd 2015):
(ns spurious-aws-sdk-helper.s3
(:use [amazonica.aws.s3])
(:require [spurious-aws-sdk-helper.utils :refer [endpoint cred]]))
(defn resource [type]
(endpoint type :spurious-s3))
(defn setup
([type]
(set-s3client-options (cred (resource type)) :path-style-access true))
([type name]
(try
(let [credentials (cred (resource type))]
(set-s3client-options credentials :path-style-access true)
(create-bucket credentials name))
(catch Exception e
(prn "S3 Error: chances are you're creating a bucket that already exists")))))
You can find the full details here: https://github.com/Integralist/spurious-clojure-aws-sdk-helper if you need more context
If I remember correctly, I needed to pass credentials through to set-s3client-options (without them it wouldn't work)

Related

How to I get the body text of a Response object returned by the fetch API in ClojureScript?

I'm trying to use the Github Gist API to get a list of all of my Gists like so:
(ns epi.core)
(.then (.fetch js/window "https://api.github.com/users/seisvelas/gists")
(fn [data] (.log js/epi data)))
js/epi is just console.log except provided by the blogging platform I'm using (epiphany.pub).
When I call that API from curl it works fine; however, when done in cljs instead of giving me the body of the response, this gives me [object Response]. Does anyone know how I can get the body text of the response?
TL;DR
(-> (.fetch js/window "https://api.github.com/users/seisvelas/gists")
(.then #(.json %)) ; Get JSON from the Response.body ReadableStream
(.then #(.log js/epi %))
is what I'd write
From ClojureScript, a JavaScript call like data.body() can be invoked with
(.body data)
and a JavaScript property access like data.body with
(.-body data)
One of those should work in your case. However, the fetch API requires a bit more if you want to get JSON from the body, which I assume you do based on the endpoint.
If you're dealing with promise chains, you might also want to consider using -> (thread-first) so it reads top to bottom.
See this Gist for more about threading promise chains.
There is a library wrapping js fetch API called lamdaisland.fetch. This library uses transit as default encoding format, so you need to specify accept format when working with github API.
This library contains kitchen-async.promise as its dependency, so you can require the kitchen-async.promise in your ClojureScript source code.
(ns fetch.demo.core
(:require [kitchen-async.promise :as p]
[lambdaisland.fetch :as fetch]))
(p/try
(p/let [resp (fetch/get
"https://api.github.com/users/seisvelas/gists"
{:accept :json
:content-type :json})]
(prn (:body resp)))
(p/catch :default e
;; log your exception here
(prn :error e)))
Seems like .fetch returns a Response object, and you need to get the attribute body from it for the body. https://developer.mozilla.org/en-US/docs/Web/API/Response
Something like (.body data)

timbre `set-config!` has changed arity thus don't know how to use it to output std err/out to a file

I am trying to use https://github.com/ptaoussanis/timbre to log to a file rather than the console. Here is some documentation I've found on how to do that:
; The default setup is simple console logging. We with to turn off console logging and
; turn on file logging to our chosen filename.
(timbre/set-config! [:appenders :standard-out :enabled?] false)
(timbre/set-config! [:appenders :spit :enabled?] true)
(timbre/set-config! [:shared-appender-config :spit-filename] log-file-name)
(timbre/set-config! [:shared-appender-config :spit-filename] log-file-name)
This works for a previous version of sente, but not for version [com.taoensso/timbre "4.3.1"]. (For unrelated reasons I need to use the latest). The issue with the above code is that set-config! now takes one argument - a hash-map. And I can't find any documentation that would help me with translating the above 'two params' code to the new 'one param' code.
I know there's a very similar question out there. This question has actual code in it so is more specific. I raised an issue as well. The code above basically comes straight from here.
To log to a file rather than the console in timbre v4.0.0 (2015 June 10) you can do the following:
(ns app.core
(:require [taoensso.timbre :as timbre]
[taoensso.timbre.appenders.core :as appenders]))
;; Disable logging to the console in timbre v4.0.0:
(timbre/merge-config! {:appenders {:println {:enabled? false}}})
;; Create a "spit to file" appender in timbre v4.0.0:
(timbre/merge-config! {:appenders {:spit (appenders/spit-appender {:fname "log.txt"})}})
Note that in timbre v4.3.1 the following does not remove nor disable the println (console) appender:
(timbre/merge-config! {:appenders {:println nil}})
The related issue can be found here https://github.com/ptaoussanis/timbre/issues/163
Addition:
As Timbre allows to modify it's config map timbre/*config* simply by using
the standard clojure API, you may also use
(timbre/swap-config! assoc-in [:appenders :spit :enabled?] false)
(timbre/swap-config! assoc-in [:appenders :spit] (appenders/spit-appender {:fname "log.txt"}))
Based on this you can additionally define a simple helper:
(def set-log-config-param! (partial timbre/swap-config! assoc-in))
and then be consistent with the timbre v3.4.0 set-config! syntax you
have quoted in your question:
(set-log-config-param! [:appenders :println :enabled?] false)
(set-log-config-param! [:appenders :spit] (appenders/spit-appender {:fname "log.txt"}))
Imho this eases the config transition between timbre v3 and v4 and makes the path to
the params more readable then the equivalent
(timbre/merge-config! {:appenders {:println {:enabled? false}}})
(timbre/merge-config! {:appenders {:spit (appenders/spit-appender {:fname "log.txt"})}})
I got a quick response from the maintainer:
"Usage of the spit (file) appender is documented in the README at https://github.com/ptaoussanis/timbre#file-appender"
And here's the code to answer the question:
;; (:require [taoensso.timbre.appenders.core :as appenders]) ; Add to ns
(timbre/merge-config!
{:appenders {:println nil ; Remove println appender
:spit (appenders/spit-appender {:fname log-file-name})}})
Unfortunately even with the :println nil mapentry the same output will go to two places. So this answer is incorrect.

Create file in Clojure

I'm trying to create rdf model with Apache Jena TDB, so here is the code:
(def model (com.hp.hpl.jena.tdb.TDBFactory/createModel "/rdfrepo"))
Which gives me following error:
Exception in thread "main" com.hp.hpl.jena.tdb.base.file.FileException: Failed to open: /rdfrepo/node2id.idn (mode=rw)
I have tried adding
:resource-paths ["shared" "resources"]
to projects.clj but didn't make any change.
I'm not very familiar with file system management in Clojure so I really need help with this one.
You can get resource file with clojure.java.io/resource
(clojure.java.io/resource "css/default.css")
=> #<URL file:/Users/yyy/xx/resources/css/default.css>
You don't need to add the / before the resource path.
You can then open the file with io/file:
(require '[clojure.java.io :as io])
(-> "file.png" io/resource io/file)
In your case you can try passing (io/resource "rdfrepo") to createModel
A path that begins with / is always at the root of the file system, which is a place you should not be trying to write. Will the code work without the leading / on the path?

"error" : "invalid_token: Could not parse auth token." When generating JWT for Firebase in Clojure

Trying to write write a wrapper around the Firebase REST API (see https://github.com/cloudfuji/taika for the full source), and the auth token seems to be failing. The functions are simple wrappers around the Firebase-provided Java library options (https://github.com/firebase/firebase-token-generator-java)
The code is simple:
(ns taika.auth
(:require [clojure.string :as string]
[clj-http.client :as client]
[cheshire.core :as json])
(:import [com.firebase.firebase-token-generator.security.token]
[org.json.JSONOBject]))
(defn token-generator [secret-key]
(com.firebase.security.token.TokenGenerator. secret-key))
(defn create-token [token-generator auth-data & [admin?]]
(let [token-options (doto (com.firebase.security.token.TokenOptions.)
(.setAdmin (or admin? false)))]
(.createToken token-generator (org.json.JSONObject. auth-data) token-options)))
Generating the token, the looks reasonable (example secret-key, of course):
(let [tg (token-generator "abc123")
auth-data {:email "example#example.com" :api_key "my-api-key"}
admin? false]
(create-token tg auth-data admin?))
=> "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ2IjowLCJpYXQiOjEzNjIxNjEzMDJ9.8701406fad76b2dff83bf38a18d91a95ed7787f255e7dd534a77e7daa0c58c59"
But when using the token in REST API requests, it fails with:
{ "error" : "invalid_token: Could not parse auth token." }
The ruby library doesn't seem to have the same problem.
Again, the full source source is at https://github.com/cloudfuji/taika/blob/master/src/taika/auth.clj
This error was caused by a bug in the Java Token Generator library. It has been fixed now. Pull down the changes and give it another shot.
https://github.com/firebase/firebase-token-generator-java

FTP with Clojure

Are there any libraries to perform FTP transfer with clojure, idiomatic to clojure, or is it necessary to use a java library such as apache commons?
Thanks
It is not necessary to use java library and you can roll complete FTP implementation in Clojure but that would be like re-inventing the wheel and not a feasible thing to do. What you can do is probably write a more functional wrapper over the Java library and then use that wrapper in your clojure code so that everything seems seamless and that't how many of the existing Java libraries are being used in Clojure.
You can use https://github.com/miner/clj-ftp either by invoking few convenience functions or by opening a client and invoking multiple commands with it.
The full API is documented in GitHub at https://github.com/miner/clj-ftp/blob/master/src/miner/ftp.clj .
Contents of project.clj
(defproject my-sweet-project "0.5.0"
:dependencies [[com.velisco/clj-ftp "0.3.0"]
; Other deps
]
; ...
)
Invoking a single FTP command
This will open new FTP connection for each command so it should be used for invoking a single command only. See the full API for complete list of these convenience functions.
(ns my-sweet-name.space
(:require [miner.ftp :as ftp]))
(defn list-files-from-ftp-server []
"Here we list contents of a directory with a convenience function"
(let [ftp-url "ftp://username:password#my.ftp.server.host:port/path/to/stuff"]
(ftp/list-files ftp-url)))
Invoking multiple commands with same connection
This will open FTP connection and invoke arbitrary amount of commands with it. This should be used when multiple commands should be invoked. The FTP connection will be automatically closed. Again check the full API for complete list of functions.
(ns my-sweet-name.space
(:require [miner.ftp :as ftp]))
(defn list-and-download-files []
"Here we list and download contents of a directory"
(let [ftp-url "ftp://username:password#my.ftp.server.host:port/path/to/stuff"]
(ftp/with-ftp [ftp-client ftp-url]
; client-file-names is used to list contents of the ftp-url
; client-get is used to download a file
(doseq [file-name (ftp/client-file-names ftp-client)]
(let [local-file-name (str "/download-path/" file-name)]
(ftp/client-get ftp-client file-name local-file-name))))))
https://github.com/miner/clj-ftp is a wrapper over Apache Commons Net.