123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579(*
* 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]*[`Chmodofintoption]*[`Linkofbooloption]*[`Checksumofstringoption]*[`Keep_git_dirofbooloption]*[`Parentsofbooloption]*[`Excludeofstringlistoption][@@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]*[`Chmodofintoption]*heredoclist*string[@@derivingsexp]typemount_bind={target:string;source:stringoption;from:stringoption;readwrite:booloption;}[@@derivingsexp]typemount_cache={id:stringoption;target:string;readonly:booloption;sharing:[`Shared|`Private|`Locked]option;from:stringoption;source:stringoption;mode:intoption;uid:intoption;gid:intoption;}[@@derivingsexp]typemount_tmpfs={target:string;size:intoption}[@@derivingsexp](* secret or ssh *)typemount_file={id:stringoption;target:stringoption;required:booloption;mode:intoption;uid:intoption;gid:intoption;}[@@derivingsexp]typemount={typ:[`Bindofmount_bind|`Cacheofmount_cache|`Tmpfsofmount_tmpfs|`Secretofmount_file|`Sshofmount_file];}[@@derivingsexp]typehealthcheck_options={interval:stringoption;timeout:stringoption;start_period:stringoption;start_interval:stringoption;retries:intoption;}[@@derivingsexp]typehealthcheck=[`Cmdofhealthcheck_options*shell_or_exec|`None][@@derivingsexp]typenetwork=[`Default|`None|`Host][@@derivingsexp]typesecurity=[`Insecure|`Sandbox][@@derivingsexp]letescape_string~char_to_escape~escapev=letlen=String.lengthvinletbuf=Buffer.createleninletj=ref0infori=0tolen-1doifString.unsafe_getvi=char_to_escape||String.unsafe_getvi=escapethen(ifi-!j>0thenBuffer.add_substringbufv!j(i-!j);Buffer.add_charbufescape;j:=i)done;Buffer.add_substringbufv!j(len-!j);Buffer.contentsbuftypeline=[`ParserDirectiveofparser_directive|`Commentofstring|`Fromoffrom|`Maintainerofstring|`Runofmountlist*networkoption*securityoption*shell_or_exec|`Run_heredocofmountlist*networkoption*securityoption*(heredoc*stringoption)list|`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=letmergemm'=ifm=m'thenmelseinvalid_arg"crunch: at least two mounts list differ."inletpackl=letrecauxacc=function|[]->acc|`Run(m,n,s,`Shella)::`Run(m',n',s',`Shellb)::tl->ifn<>n'theninvalid_arg"crunch: at least two networks differ.";ifs<>s'theninvalid_arg"crunch: at least two securities differ.";aux(`Run(mergemm',n,s,`Shells[a;b])::acc)tl|`Run(m,n,s,`Shellsa)::`Run(m',n',s',`Shellb)::tl->ifn<>n'theninvalid_arg"crunch: at least two networks differ.";ifs<>s'theninvalid_arg"crunch: at least two securities differ.";aux(`Run(mergemm',n,s,`Shells(a@[b]))::acc)tl|`Run(m,n,s,`Shellsa)::`Run(m',n',s',`Shellsb)::tl->ifn<>n'theninvalid_arg"crunch: at least two networks differ.";ifs<>s'theninvalid_arg"crunch: at least two securities differ.";aux(`Run(mergemm',n,s,`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=escape_string~char_to_escape:'"'letstring_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_int_octalname=function|None->[]|Somevalue->[sprintf"%s=%04o"namevalue]letoptional_boolname=function|None->[]|Somevalue->[sprintf"%s=%b"namevalue]letoptional_flagname=function|Sometrue->[name]|Somefalse|None->[]letoptional_enumnamestring_of_val=function|None->[]|Somevalue->[sprintf"--%s=%s"name(string_of_valvalue)]letoptional_listnamestring_of_val=function|None|Some[]->[]|Somelist->List.map(fune->sprintf"--%s=%s"name(string_of_vale))listletstring_of_sources_to_dest(t:sources_to_dest)=let(`Fromfrm,`Srcsl,`Dstd,`Chownchown,`Chmodchmod,`Linklink,`Checksumchecksum,`Keep_git_dirkeep_git_dir,`Parentsparents,`Excludeexclude)=tinString.concat" "(optional_flag"--link"link@optional"--chown"chown@optional_int_octal"--chmod"chmod@optional"--from"frm@optional"--checksum"checksum@optional_bool"--keep-git-dir"keep_git_dir@optional_bool"--parents"parents@optional_list"--exclude"Fun.idexclude@[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,`Chmodchmod,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@optional_int_octal"--chmod"chmod@List.revheader@[dst])^docsletstring_of_mount{typ}=matchtypwith|`Bind{target;source;from;readwrite}->String.concat","(["--mount=type=bind"]@[sprintf"target=%s"target]@optional"source"source@optional"from"from@optional_bool"readwrite"readwrite)|`Cache{id;target;readonly;sharing;from;source;mode;uid;gid}->String.concat","(["--mount=type=cache"]@optional"id"id@[sprintf"target=%s"target]@optional_bool"readonly"readonly@(matchsharingwith|None->[]|Some`Shared->["sharing=shared"]|Some`Private->["sharing=private"]|Some`Locked->["sharing=locked"])@optional"from"from@optional"source"source@optional_int_octal"mode"mode@optional_int"uid"uid@optional_int"gid"gid)|`Tmpfs{target;size}->String.concat","(["--mount=type=bind"]@[sprintf"target=%s"target]@optional_int"size"size)|`Sshm|`Secretm->lettyp=matchtypwith|`Ssh_->"ssh"|`Secret_->"secret"|_->assertfalseinlet{id;target;required;mode;uid;gid}=minString.concat","([sprintf"--mount=type=%s"typ]@optional"id"id@optional"target"target@optional_bool"required"required@optional_int_octal"mode"mode@optional_int"uid"uid@optional_int"gid"gid)letstring_of_run'~escapemountsnetworksecurity=letmounts=mounts|>List.mapstring_of_mount|>List.map(escape_string~char_to_escape:' '~escape)inletnetwork=optional_enum"network"(function`Default->"default"|`None->"none"|`Host->"host")networkinletsecurity=optional_enum"security"(function`Insecure->"insecure"|`Sandbox->"sandbox")securityinmounts@network@securityletstring_of_run~escapemountsnetworksecurityc=letparams=string_of_run'~escapemountsnetworksecurityinletrun=string_of_shell_or_exec~escapecinString.concat" "(params@[run])letstring_of_run_heredoc~escapemountsnetworksecurityc=letparams=string_of_run'~escapemountsnetworksecurityinletescape_cmd=function|Somecmd->" "^escape_string~char_to_escape:'\n'~escapecmd|None->""inletcmds,docs=List.fold_left(fun(cmds,docs)(t,cmd)->letcmd=escape_cmdcmdin(cmds@[sprintf"<<%s%s%s"(ift.stripthen"-"else"")t.wordcmd],sprintf"%s\n%s\n%s"docst.here_documentt.delimiter))([],"")cinString.concat" "(params@[String.concat" && "cmds])^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|`Run(mounts,network,security,c)->cmd"RUN"(string_of_run~escapemountsnetworksecurityc)|`Run_heredoc(mounts,network,security,c)->cmd"RUN"(string_of_run_heredoc~escapemountsnetworksecurityc)|`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"--start-interval"options.start_interval@optional_int"--retries"options.retries)^sprintf" %c\n %s"escape(string_of_line~escape(`Cmdc))(* Function interface *)letparser_directivepd:t=[`ParserDirectivepd]letbuildkit_syntax=parser_directive(`Syntax"docker/dockerfile:1")letheredoc?(strip=false)?(word="EOF")?(delimiter=word)fmt=ksprintf(funhere_document->{here_document;strip;word;delimiter})fmtletmount_bind~target?source?from?readwrite()=letm={target;source;from;readwrite}in{typ=`Bindm}letmount_cache?id~target?readonly?sharing?from?source?mode?uid?gid()=letm={id;target;readonly;sharing;from;source;mode;uid;gid}in{typ=`Cachem}letmount_tmpfs~target?size()=letm={target;size}in{typ=`Tmpfsm}letmount_secret?id?target?required?mode?uid?gid()=letm={id;target;required;mode;uid;gid}in{typ=`Secretm}letmount_ssh?id?target?required?mode?uid?gid()=letm={id;target;required;mode;uid;gid}in{typ=`Sshm}letfrom?alias?tag?platformimage=[`From{image;tag;alias;platform}]letcommentfmt=ksprintf(func->[`Commentc])fmtletmaintainerfmt=ksprintf(funm->[`Maintainerm])fmtletrun?(mounts=[])?network?securityfmt=ksprintf(funb->[`Run(mounts,network,security,`Shellb)])fmtletrun_exec?(mounts=[])?network?securitycmds:t=[`Run(mounts,network,security,`Execcmds)]letrun_heredoc?(mounts=[])?network?securitydocs:t=[`Run_heredoc(mounts,network,security,docs)]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?chmod?from?exclude?checksum?keep_git_dir~src~dst():t=[`Add(`Fromfrom,`Srcsrc,`Dstdst,`Chownchown,`Chmodchmod,`Linklink,`Checksumchecksum,`Keep_git_dirkeep_git_dir,`ParentsNone,`Excludeexclude);]letcopy?link?chown?chmod?from?parents?exclude~src~dst():t=[`Copy(`Fromfrom,`Srcsrc,`Dstdst,`Chownchown,`Chmodchmod,`Linklink,`ChecksumNone,`Keep_git_dirNone,`Parentsparents,`Excludeexclude);]letcopy_heredoc?chown?chmod~src~dst():t=[`Copy_heredoc(`Chownchown,`Chmodchmod,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?start_interval?retriesfmt=letopts={interval;timeout;start_period;start_interval;retries}inksprintf(funb->[`Healthcheck(`Cmd(opts,`Shellb))])fmtlethealthcheck_exec?interval?timeout?start_period?start_interval?retriescmds:t=letopts={interval;timeout;start_period;start_interval;retries}in[`Healthcheck(`Cmd(opts,`Execcmds))]lethealthcheck_none():t=[`Healthcheck`None]letstring_of_ttl=letrecfind_escape=function|`ParserDirective(`Escapec)::_->c|`ParserDirective_::tl->find_escapetl|_->'\\'inletescape=find_escapetlinletbuf=Buffer.create4096inletis_parser_directive=function`ParserDirective_->true|_->falseandis_arg=function`Arg_->true|_->falseinletspacel=Buffer.add_stringbuf(string_of_line~escapel);Buffer.add_stringbuf"\n\n"andprintl=Buffer.add_stringbuf(string_of_line~escapel);Buffer.add_charbuf'\n'inletrecoutside=function|(`ParserDirective_asl1)::l2::tlwhennot(is_parser_directivel2)->spacel1;outside(l2::tl)|(`Arg_asl1)::l2::tlwhennot(is_argl2)->spacel1;outside(l2::tl)|(`From_asl)::tl->printl;insidetl|l::tl->printl;outsidetl|[]->()andinside=function|(`Comment_asl1)::(`From_asl2)::tl->Buffer.add_stringbuf"\n";printl1;inside(l2::tl)|l1::(`From_asl2)::tl->spacel1;inside(l2::tl)|l::tl->printl;insidetl|[]->()inoutsidetl;Buffer.contentsbufletppppftl=Fmt.pfppf"%s"(string_of_ttl)