123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292(*
* Copyright (c) 2014-2016 Anil Madhavapeddy <anil@recoil.org>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
*)openSexplib.Convtypeshell_or_exec=[`Shellofstring|`Shellsofstringlist|`Execofstringlist][@@derivingsexp]typesources_to_dest=[`Fromofstringoption]*[`Srcofstringlist]*[`Dstofstring]*[`Chownofstringoption]*[`Linkofbooloption][@@derivingsexp]typefrom={image:string;tag:stringoption;alias:stringoption;platform:stringoption;}[@@derivingsexp]typeparser_directive=[`Syntaxofstring|`Escapeofchar][@@derivingsexp]typeheredoc={here_document:string;word:string;delimiter:string;strip:bool;}[@@derivingsexp]typeheredocs_to_dest=[`Chownofstringoption]*heredoclist*string[@@derivingsexp]typehealthcheck_options={interval:stringoption;timeout:stringoption;start_period:stringoption;retries:intoption;}[@@derivingsexp]typehealthcheck=[`Cmdofhealthcheck_options*shell_or_exec|`None][@@derivingsexp]typeline=[`ParserDirectiveofparser_directive|`Commentofstring|`Fromoffrom|`Maintainerofstring|`Runofshell_or_exec|`Cmdofshell_or_exec|`Exposeofintlist|`Argofstring*stringoption|`Envof(string*string)list|`Addofsources_to_dest|`Copyofsources_to_dest|`Copy_heredocofheredocs_to_dest|`Entrypointofshell_or_exec|`Shellofstringlist|`Volumeofstringlist|`Userofstring|`Workdirofstring|`Onbuildofline|`Labelof(string*string)list|`Healthcheckofhealthcheck|`Stopsignalofstring][@@derivingsexp]typet=linelist[@@derivingsexp]let(@@)=(@)let(@@@)=List.fold_left(funab->a@@b)letempty=[]letmaybef=functionNone->empty|Somev->fvopenPrintf(* Multiple RUN lines will be compressed into a single one in
order to reduce the number of layers used *)letcrunchl=letpackl=letrecauxacc=function|[]->acc|`Run(`Shella)::`Run(`Shellb)::tl->aux(`Run(`Shells[a;b])::acc)tl|`Run(`Shellsa)::`Run(`Shellb)::tl->aux(`Run(`Shells(a@[b]))::acc)tl|`Run(`Shellsa)::`Run(`Shellsb)::tl->aux(`Run(`Shells(a@b))::acc)tl|hd::tl->aux(hd::acc)tlinList.rev(aux[]l)inletrecfixpfnl=leta=fnlinifa=lthenlelsefixpfnainfixppacklletquotes=sprintf"%S"sletcmdcr=c^" "^rletjson_array_of_listsl=sprintf"[ %s ]"(String.concat", "(List.mapquotesl))letstring_of_shell_or_exec~escape(t:shell_or_exec)=matchtwith|`Shells->s|`Shells[]->""|`Shells[s]->s|`Shellsl->String.concat(" && "^String.make1escape^"\n ")l|`Execsl->json_array_of_listslletquote_env_var~escapev=letlen=String.lengthvinletbuf=Buffer.createleninletj=ref0infori=0tolen-1doifv.[i]='"'||v.[i]=escapethen(ifi-!j>0thenBuffer.add_substringbufv!j(i-!j);Buffer.add_charbufescape;j:=i)done;Buffer.add_substringbufv!j(len-!j);Buffer.contentsbufletstring_of_env_var~escape(name,value)=sprintf{|%s="%s"|}name(quote_env_var~escapevalue)letstring_of_env_list~escapeel=List.map(string_of_env_var~escape)el|>String.concat" "letstring_of_arg~escape=function|name,Somevalue->string_of_env_var~escape(name,value)|name,None->nameletoptionalname=function|None->[]|Somevalue->[sprintf"%s=%s"namevalue]letoptional_intname=function|None->[]|Somevalue->[sprintf"%s=%d"namevalue]letoptional_flagname=function|Sometrue->[name]|Somefalse|None->[]letstring_of_sources_to_dest(t:sources_to_dest)=let`Fromfrm,`Srcsl,`Dstd,`Chownchown,`Linklink=tinString.concat" "(optional_flag"--link"link@optional"--chown"chown@optional"--from"frm@[json_array_of_list(sl@[d])])letstring_of_label_listls=List.map(fun(k,v)->sprintf"%s=%S"kv)ls|>String.concat" "letstring_of_copy_heredoc(t:heredocs_to_dest)=let`Chownchown,heredocs,dst=tinletheader,docs=List.fold_left(fun(header,docs)t->(sprintf"<<%s%s"(ift.stripthen"-"else"")t.word::header,sprintf"%s\n%s\n%s"docst.here_documentt.delimiter))([],"")heredocsinString.concat" "(optional"--chown"chown@List.revheader@[dst])^docsletrecstring_of_line~escape(t:line)=matchtwith|`ParserDirective(`Escapec)->cmd"#"("escape="^String.make1c)|`ParserDirective(`Syntaxstr)->cmd"#"("syntax="^str)|`Commentc->cmd"#"c|`From{image;tag;alias;platform}->cmd"FROM"(String.concat""[(matchplatformwith|None->""|Somep->"--platform="^p^" ");image;(matchtagwithNone->""|Somet->":"^t);(matchaliaswithNone->""|Somea->" as "^a);])|`Maintainerm->cmd"MAINTAINER"m|`Runc->cmd"RUN"(string_of_shell_or_exec~escapec)|`Cmdc->cmd"CMD"(string_of_shell_or_exec~escapec)|`Exposepl->cmd"EXPOSE"(String.concat" "(List.mapstring_of_intpl))|`Arga->cmd"ARG"(string_of_arg~escapea)|`Envel->cmd"ENV"(string_of_env_list~escapeel)|`Addc->cmd"ADD"(string_of_sources_to_destc)|`Copyc->cmd"COPY"(string_of_sources_to_destc)|`Copy_heredocc->cmd"COPY"(string_of_copy_heredocc)|`Useru->cmd"USER"u|`Volumevl->cmd"VOLUME"(json_array_of_listvl)|`Entrypointel->cmd"ENTRYPOINT"(string_of_shell_or_exec~escapeel)|`Shellsl->cmd"SHELL"(json_array_of_listsl)|`Workdirwd->cmd"WORKDIR"wd|`Onbuildt->cmd"ONBUILD"(string_of_line~escapet)|`Labells->cmd"LABEL"(string_of_label_listls)|`Stopsignals->cmd"STOPSIGNAL"s|`Healthcheck(`Cmd(opts,c))->cmd"HEALTHCHECK"(string_of_healthcheck~escapeoptsc)|`Healthcheck`None->"HEALTHCHECK NONE"andstring_of_healthcheck~escapeoptionsc=String.concat" "(optional"--interval"options.interval@optional"--timeout"options.timeout@optional"--start-period"options.start_period@optional_int"--retries"options.retries)^sprintf" %c\n %s"escape(string_of_line~escape(`Cmdc))(* Function interface *)letparser_directivepd:t=[`ParserDirectivepd]letheredoc?(strip=false)?(word="EOF")?(delimiter=word)fmt=ksprintf(funhere_document->{here_document;strip;word;delimiter})fmtletfrom?alias?tag?platformimage=[`From{image;tag;alias;platform}]letcommentfmt=ksprintf(func->[`Commentc])fmtletmaintainerfmt=ksprintf(funm->[`Maintainerm])fmtletrunfmt=ksprintf(funb->[`Run(`Shellb)])fmtletrun_execcmds:t=[`Run(`Execcmds)]letcmdfmt=ksprintf(funb->[`Cmd(`Shellb)])fmtletcmd_execcmds:t=[`Cmd(`Execcmds)]letexpose_portp:t=[`Expose[p]]letexpose_portsp:t=[`Exposep]letarg?defaulta:t=[`Arg(a,default)]letenve:t=[`Enve]letadd?link?chown?from~src~dst():t=[`Add(`Fromfrom,`Srcsrc,`Dstdst,`Chownchown,`Linklink)]letcopy?link?chown?from~src~dst():t=[`Copy(`Fromfrom,`Srcsrc,`Dstdst,`Chownchown,`Linklink)]letcopy_heredoc?chown~src~dst():t=[`Copy_heredoc(`Chownchown,src,dst)]letuserfmt=ksprintf(funu->[`Useru])fmtletonbuildt=List.map(funl->`Onbuildl)tletvolumefmt=ksprintf(funv->[`Volume[v]])fmtletvolumesv:t=[`Volumev]letlabells=[`Labells]letentrypointfmt=ksprintf(fune->[`Entrypoint(`Shelle)])fmtletentrypoint_exece:t=[`Entrypoint(`Exece)]letshells:t=[`Shells]letworkdirfmt=ksprintf(funwd->[`Workdirwd])fmtletstopsignals=[`Stopsignals]lethealthcheck?interval?timeout?start_period?retriesfmt=letopts={interval;timeout;start_period;retries}inksprintf(funb->[`Healthcheck(`Cmd(opts,`Shellb))])fmtlethealthcheck_exec?interval?timeout?start_period?retriescmds:t=letopts={interval;timeout;start_period;retries}in[`Healthcheck(`Cmd(opts,`Execcmds))]lethealthcheck_none():t=[`Healthcheck`None]letstring_of_ttl=letrecfind_escape=function|`ParserDirective(`Escapec)::_->c|`ParserDirective_::tl->find_escapetl|_->'\\'inString.concat"\n"(List.map(string_of_line~escape:(find_escapetl))tl)letppppftl=Fmt.pfppf"%s"(string_of_ttl)