Skip to content

Commit 8146d13

Browse files
authored
Merge pull request #27 from cryptosense/add-put-upload-method
Add PUT upload method
2 parents e60509b + 579ea54 commit 8146d13

File tree

15 files changed

+200
-101
lines changed

15 files changed

+200
-101
lines changed

.github/workflows/main.yml

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,11 @@ jobs:
1010
fail-fast: false
1111
matrix:
1212
os:
13-
- ubuntu-18.04
13+
- ubuntu-20.04
1414
- ubuntu-22.04
1515
- windows-latest
1616
ocaml-compiler:
17-
- 4.13.x
17+
- 4.14.x
1818
runs-on: ${{ matrix.os }}
1919
steps:
2020
- name: Checkout code
@@ -33,7 +33,8 @@ jobs:
3333
with:
3434
ocaml-compiler: ${{ matrix.ocaml-compiler }}
3535
opam-repositories: |
36-
default: 'https://github.com/fdopen/opam-repository-mingw.git#opam2'
36+
opam-repository-mingw: https://github.com/ocaml-opam/opam-repository-mingw.git#sunset
37+
default: https://github.com/ocaml/opam-repository.git
3738
- run: opam pin add cs_api_client.dev . --no-action
3839
- run: opam depext cs_api_client --yes --with-test
3940
- run: opam install . --deps-only --with-test

.ocamlformat

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
version=0.19.0
1+
version=0.25.1
22
break-cases=all
33
break-fun-decl=fit-or-vertical
44
break-fun-sig=fit-or-vertical

api/api.ml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ end
2828
module Data = struct
2929
type t =
3030
| Raw of string
31+
| File of File.t
3132
| Multipart of Part.t list
3233
[@@deriving eq, ord, show]
3334

@@ -39,6 +40,7 @@ module Method = struct
3940
type t =
4041
| Get
4142
| Post
43+
| Put
4244
[@@deriving eq, ord, show]
4345
end
4446

api/api.mli

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ end
2828
module Data : sig
2929
type t =
3030
| Raw of string
31+
| File of File.t
3132
| Multipart of Part.t list
3233
[@@deriving eq, ord, show]
3334

@@ -38,6 +39,7 @@ module Method : sig
3839
type t =
3940
| Get
4041
| Post
42+
| Put
4143
end
4244

4345
module Request : sig

cs_api_cli/cs_api_cli.ml

Lines changed: 50 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -37,9 +37,9 @@ let resolve_project_name ~client ~api ~project_id ~project_name =
3737
let rec analyze_trace ~client ~trace_id ~api ~count profile_id =
3838
let open Lwt.Infix in
3939
(let open Lwt_result.Infix in
40-
Cs_api_core.build_analyze_request ~api ~trace_id ~profile_id
41-
|> Cs_api_io.send_request ~client
42-
>>= Cs_api_io.get_response_graphql)
40+
Cs_api_core.build_analyze_request ~api ~trace_id ~profile_id
41+
|> Cs_api_io.send_request ~client
42+
>>= Cs_api_io.get_response_graphql)
4343
>>= function
4444
| Error "Not found" ->
4545
Printf.printf "Profile ID not found\n";
@@ -66,32 +66,37 @@ let upload_trace
6666
~api =
6767
let open Lwt.Infix in
6868
(let open Lwt_result.Infix in
69-
get_file trace_file >>= fun file ->
70-
resolve_project_name ~client ~api ~project_id ~project_name
71-
>>= fun project_id ->
72-
Cs_api_core.build_s3_signed_post_request ~api
73-
|> Cs_api_io.send_request ~client
74-
>>= Cs_api_io.get_response_graphql
75-
>>= (fun body ->
76-
match Cs_api_core.parse_s3_signature_request ~body with
77-
| None ->
78-
Lwt.return (Error "Failed to parse S3 signature request response")
79-
| Some (s3_url, s3_signature) ->
80-
Lwt.return
81-
(Ok
82-
(Cs_api_core.build_file_upload_request ~s3_url ~s3_signature
83-
~file)))
84-
>>= Cs_api_io.send_request ~client
85-
>>= Cs_api_io.get_response
86-
>>= (fun body ->
87-
let s3_key = Cs_api_core.parse_s3_response ~body in
88-
let import_request =
89-
Cs_api_core.build_trace_import_request ~slot_name ~api ~project_id
90-
~s3_key ~trace_name ~file
91-
in
92-
Lwt.return (Ok import_request))
93-
>>= Cs_api_io.send_request ~client
94-
>>= Cs_api_io.get_response_graphql)
69+
get_file trace_file >>= fun file ->
70+
resolve_project_name ~client ~api ~project_id ~project_name
71+
>>= fun project_id ->
72+
Cs_api_core.build_s3_signed_post_request ~api
73+
|> Cs_api_io.send_request ~client
74+
>>= Cs_api_io.get_response_graphql
75+
>>= (fun body ->
76+
match Cs_api_core.parse_s3_signature_request ~body with
77+
| None ->
78+
Lwt.return (Error "Failed to parse S3 signature request response")
79+
| Some (s3_url, s3_method, s3_signature) ->
80+
Lwt.return
81+
(Ok
82+
( s3_url
83+
, Cs_api_core.build_file_upload_request ~s3_url ~s3_method
84+
~s3_signature ~file )))
85+
>>= (fun (url, request) ->
86+
( Cs_api_io.send_request ~client request >>= fun response ->
87+
Cs_api_io.get_response response )
88+
>>= fun body ->
89+
match Cs_api_core.parse_s3_response ~body with
90+
| Ok s3_key -> Lwt_result.return s3_key
91+
| Error _ -> Lwt.return (Cs_api_core.parse_s3_url url))
92+
>>= (fun s3_key ->
93+
let import_request =
94+
Cs_api_core.build_trace_import_request ~slot_name ~api ~project_id
95+
~s3_key ~trace_name ~file
96+
in
97+
Lwt.return (Ok import_request))
98+
>>= Cs_api_io.send_request ~client
99+
>>= Cs_api_io.get_response_graphql)
95100
>>= function
96101
| Ok body ->
97102
let trace_id = Cs_api_core.get_id_from_trace_import_response_body ~body in
@@ -107,10 +112,10 @@ let upload_trace
107112
let list_profiles ~client ~api =
108113
let open Lwt.Infix in
109114
(let open Lwt_result.Infix in
110-
Cs_api_core.build_list_profiles_request ~api
111-
|> Cs_api_io.send_request ~client
112-
>>= Cs_api_io.get_response_graphql
113-
>|= fun body -> Cs_api_core.parse_list_profiles_response ~body)
115+
Cs_api_core.build_list_profiles_request ~api
116+
|> Cs_api_io.send_request ~client
117+
>>= Cs_api_io.get_response_graphql
118+
>|= fun body -> Cs_api_core.parse_list_profiles_response ~body)
114119
>|= function
115120
| Error message ->
116121
Printf.printf "%s\n" message;
@@ -184,7 +189,7 @@ let api_endpoint =
184189

185190
let api_key =
186191
let doc = "API key" in
187-
let env = Cmdliner.Arg.env_var "CRYPTOSENSE_API_KEY" ~doc in
192+
let env = Cmdliner.Cmd.Env.info "CRYPTOSENSE_API_KEY" ~doc in
188193
let doc =
189194
"API key - can also be defined using the CRYPTOSENSE_API_KEY environment \
190195
variable"
@@ -259,10 +264,10 @@ let list_profiles_term =
259264
$ no_check_certificate)
260265

261266
let list_profiles_info =
262-
Cmdliner.Term.info "list-profiles"
267+
Cmdliner.Cmd.info "list-profiles"
263268
~doc:"List the available profiles of the Cryptosense Analyzer platform"
264269

265-
let list_profiles_cmd = (list_profiles_term, list_profiles_info)
270+
let list_profiles_cmd = Cmdliner.Cmd.v list_profiles_info list_profiles_term
266271

267272
let analyze_term =
268273
Cmdliner.Term.(
@@ -275,9 +280,9 @@ let analyze_term =
275280
$ no_check_certificate)
276281

277282
let analyze_info =
278-
Cmdliner.Term.info "analyze" ~doc:"Analyze a trace to create a report"
283+
Cmdliner.Cmd.info "analyze" ~doc:"Analyze a trace to create a report"
279284

280-
let analyze_cmd = (analyze_term, analyze_info)
285+
let analyze_cmd = Cmdliner.Cmd.v analyze_info analyze_term
281286

282287
let upload_trace_term =
283288
Cmdliner.Term.(
@@ -294,19 +299,19 @@ let upload_trace_term =
294299
$ no_check_certificate)
295300

296301
let upload_trace_info =
297-
Cmdliner.Term.info "upload-trace"
302+
Cmdliner.Cmd.info "upload-trace"
298303
~doc:"Upload a trace to the Cryptosense Analyzer platform"
299304

300-
let upload_trace_cmd = (upload_trace_term, upload_trace_info)
305+
let upload_trace_cmd = Cmdliner.Cmd.v upload_trace_info upload_trace_term
301306

302307
let default_term =
303308
Cmdliner.Term.(ret (const (`Error (true, "Missing command"))))
304309

305-
let default_info = Cmdliner.Term.info "cs-api" ~version:"%%VERSION_NUM%%"
306-
307-
let default_cmd = (default_term, default_info)
310+
let default_info = Cmdliner.Cmd.info "cs-api" ~version:"%%VERSION_NUM%%"
311+
let default_cmd = default_info
308312

309313
let () =
310-
Cmdliner.Term.eval_choice default_cmd
314+
Cmdliner.Cmd.group ~default:default_term default_cmd
311315
[analyze_cmd; list_profiles_cmd; upload_trace_cmd]
312-
|> Cmdliner.Term.exit_status
316+
|> Cmdliner.Cmd.eval'
317+
|> Stdlib.exit

cs_api_client.opam

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,13 @@ run-test: [
1414
depends: [
1515
"alcotest" {with-test}
1616
"base64"
17-
"cmdliner" {= "1.0.4"}
17+
"cmdliner" {>= "1.2.0"}
1818
"containers" {>= "3.6"}
1919
"dune" {>= "2.7.0"}
2020
"lwt"
2121
"lwt_ppx"
22-
"ocaml" {>= "4.13.0"}
23-
"ocamlformat" {= "0.19.0" & with-test}
22+
"ocaml" {>= "4.14.0"}
23+
"ocamlformat" {= "0.25.1" & with-test}
2424
"ocurl" {< "0.9.2"} # For compilation with old libcurl
2525
"ppx_deriving"
2626
"terminal_size" {>= "0.2.0"} # For compilation on Windows

cs_api_core/cs_api_core.ml

Lines changed: 55 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ module Graphql = struct
1717
generateTraceUploadPost(input: {}) {
1818
url
1919
formData
20+
method
2021
}
2122
}
2223
|}
@@ -146,6 +147,7 @@ let parse_s3_signature_request ~body =
146147
let json = Yojson.Basic.from_string body in
147148
let data = json |> member "data" |> member "generateTraceUploadPost" in
148149
let url = data |> member "url" |> to_string_option in
150+
let method_ = data |> member "method" |> to_string_option in
149151
let formData =
150152
match data |> member "formData" with
151153
| `String str -> Yojson.Basic.from_string str
@@ -162,28 +164,46 @@ let parse_s3_signature_request ~body =
162164
formData |> member "success_action_status" |> to_int_option
163165
in
164166
url >>= fun url ->
165-
signature >>= fun signature ->
166-
key >>= fun key ->
167-
credential >>= fun credential ->
168-
date >>= fun date ->
169-
algorithm >>= fun algorithm ->
170-
policy >>= fun policy ->
171-
acl >>= fun acl ->
172-
success_action_status >|= fun success_action_status ->
173-
( url
174-
, [ ("x-amz-signature", signature)
175-
; ("x-amz-credential", credential)
176-
; ("x-amz-algorithm", algorithm)
177-
; ("x-amz-date", date)
178-
; ("key", key)
179-
; ("policy", policy)
180-
; ("acl", acl)
181-
; ("success_action_status", string_of_int success_action_status) ] )
167+
method_ >>= fun method_ ->
168+
if String.equal method_ "POST" then
169+
signature >>= fun signature ->
170+
key >>= fun key ->
171+
credential >>= fun credential ->
172+
date >>= fun date ->
173+
algorithm >>= fun algorithm ->
174+
policy >>= fun policy ->
175+
acl >>= fun acl ->
176+
success_action_status >|= fun success_action_status ->
177+
( url
178+
, Api.Method.Post
179+
, [ ("x-amz-signature", signature)
180+
; ("x-amz-credential", credential)
181+
; ("x-amz-algorithm", algorithm)
182+
; ("x-amz-date", date)
183+
; ("key", key)
184+
; ("policy", policy)
185+
; ("acl", acl)
186+
; ("success_action_status", string_of_int success_action_status) ] )
187+
else if String.equal method_ "PUT" then
188+
Some (url, Api.Method.Put, [])
189+
else
190+
raise (Invalid_argument ("Unknown method: " ^ method_))
182191

183192
let parse_s3_response ~body =
184-
let key_extractor = Str.regexp "<Key>\\([^<>]*\\)</Key>" in
185-
let _ = Str.search_forward key_extractor body 0 in
186-
Str.matched_group 1 body
193+
try
194+
let key_extractor = Str.regexp "<Key>\\([^<>]*\\)</Key>" in
195+
let _ = Str.search_forward key_extractor body 0 in
196+
Ok (Str.matched_group 1 body)
197+
with
198+
| Not_found -> Error "Key could not be extracted from S3 response."
199+
200+
let parse_s3_url url =
201+
try
202+
let key_extractor = Str.regexp "/uploads/\\([a-z0-9]+\\)" in
203+
let _ = Str.search_forward key_extractor url 0 in
204+
Ok ("uploads/" ^ Str.matched_group 1 url)
205+
with
206+
| Not_found -> Error "Key could not be extracted from S3 URL."
187207

188208
let build_s3_signed_post_request ~api =
189209
let {Api.endpoint; key} = api in
@@ -197,17 +217,27 @@ let build_s3_signed_post_request ~api =
197217
[ ("query", `String Graphql.generate_trace_upload_post)
198218
; ("variables", `Assoc []) ])) }
199219

200-
let build_file_upload_request ~s3_url ~s3_signature ~(file : Api.File.t) =
220+
let build_file_upload_request
221+
~s3_url
222+
~s3_method
223+
~s3_signature
224+
~(file : Api.File.t) =
201225
let direct_fields =
202226
Api.Data.multipart_from_assoc
203227
(s3_signature
204228
@ [ ("Content-Type", "")
205229
; ("x-amz-meta-filename", Filename.basename file.path) ])
206230
in
207-
{ Api.Request.url = s3_url
208-
; header = []
209-
; method_ = Post
210-
; data = Multipart (direct_fields @ [{name = "file"; content = File file}]) }
231+
match s3_method with
232+
| Api.Method.Post ->
233+
{ Api.Request.url = s3_url
234+
; header = []
235+
; method_ = Post
236+
; data = Multipart (direct_fields @ [{name = "file"; content = File file}])
237+
}
238+
| Put ->
239+
{Api.Request.url = s3_url; header = []; method_ = Put; data = File file}
240+
| Get -> raise (Invalid_argument "Unsupported method: GET")
211241

212242
let build_trace_import_request
213243
~api

cs_api_core/cs_api_core.mli

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,17 @@
11
module Graphql : sig
22
val to_global_id : type_:string -> id:int -> string
3-
43
val generate_trace_upload_post : string
5-
64
val create_trace : string
7-
85
val analyze_trace : string
9-
106
val list_profiles : string
117
end
128

139
val parse_s3_signature_request :
14-
body:string -> (string * Api.S3Signature.t) option
10+
body:string -> (string * Api.Method.t * Api.S3Signature.t) option
1511
(** Response parsing functions **)
1612

17-
val parse_s3_response : body:string -> string
13+
val parse_s3_response : body:string -> (string, string) result
14+
val parse_s3_url : string -> (string, string) result
1815

1916
val build_s3_signed_post_request : api:Api.t -> Api.Request.t
2017
(** Request building functions **)
@@ -25,11 +22,11 @@ val build_search_project_by_name_request :
2522
api:Api.t -> name:string -> Api.Request.t
2623

2724
val parse_list_profiles_response : body:string -> (string * int * string) list
28-
2925
val parse_search_project_by_name_response : body:string -> int option
3026

3127
val build_file_upload_request :
3228
s3_url:string
29+
-> s3_method:Api.Method.t
3330
-> s3_signature:Api.S3Signature.t
3431
-> file:Api.File.t
3532
-> Api.Request.t
@@ -47,5 +44,4 @@ val build_analyze_request :
4744
api:Api.t -> trace_id:int -> profile_id:int -> Api.Request.t
4845

4946
val get_id_from_trace_import_response_body : body:string -> int
50-
5147
val get_info_from_analyze_response_body : body:string -> string * int

0 commit comments

Comments
 (0)