55

I haven't yet come across a Chef resource that will copy/move files locally. For example, I want to download jetty hightide and unzip it. Once done, copy all the files into a particular folder, like this:

# mv /var/tmp/jetty-hightide-7.4.5.v20110725/* /opt/jetty/

BTW, jettyhightide when unzipped, gives you a folder and inside that folder rest of the files are located. Hence unzip jetty-hightide-7.4.5.v20110725.zip -d /opt/jetty/ is useless because it will create a directory /opt/jetty/jetty-hightide-7.4.5.v20110725/* whereas what I really want is /opt/jetty/*. Hence I am looking for a local copy/move resource in Chef.

codeforester
  • 39,467
  • 16
  • 112
  • 140
slayedbylucifer
  • 22,878
  • 16
  • 94
  • 123
  • can you please help me out on the below question? http://stackoverflow.com/questions/39105976/mv-resource-in-chef/39108026#39108026 – Raja Aug 24 '16 at 13:21
  • you say "copy all the files" but then you show a "mv" command, which moves *not* copies – JoelFan Dec 07 '17 at 02:25

8 Answers8

112

How to copy single file

First way

I use file statement to copy file (compile-time check)

file "/etc/init.d/someService" do
  owner 'root'
  group 'root'
  mode 0755
  content ::File.open("/home/someService").read
  action :create
end

here :

  • "/etc/init.d/someService" - target file,
  • "/home/someService" - source file

Also you can wrap ::File.open("/home/someService").read in lazy block

...
lazy { ::File.open("/home/someService").read }
...

Second way

User remote_file statement (run-time check)

remote_file "Copy service file" do 
  path "/etc/init.d/someService" 
  source "file:///home/someService"
  owner 'root'
  group 'root'
  mode 0755
end

Third way

Also you can use shell/batch

For-each directory

Dir[ "/some/directory/resources/**/*" ].each do |curr_path|
  file "/some/target/dir/#{Pathname.new(curr_path).basename}" do
    owner 'root'
    group 'root'
    mode 0755
    content lazy { IO.read(curr_path, mode: 'rb').read }
    action :create
  end if File.file?(curr_path)
  directory "/some/target/dir/#{File.dirname(curr_path)}" do
    path curr_path
    owner 'root'
    group 'root'
    mode 0755
    action :create
  end if File.directory?(curr_path)
end

This is just idea, because sub-paths in this example is not handled correctly.

CAMOBAP
  • 5,523
  • 8
  • 58
  • 93
  • Thanks for the thorough answer. I wish the `file` resource had a `content_file` attribute or something. This seems like a fairly common thing to do. – Matt Kantor Nov 14 '13 at 17:39
  • 5
    Great answer! I especially like the 'First way' above, since if the file contents aren't changed it will not update the file. It's worth mentioning that if the source file is managed by Chef, then the ::File.open *must* be wrapped in a lazy block, otherwise Chef will fail to find the file at compile time. The lazy attribute is only available in Chef >= 11.6, >= 10.28 (I believe). – Jon-Erik Mar 14 '14 at 17:21
  • 4
    It's good to point out that using a remote file will get around Chef requiring the file to exist at compile time. – SamG Jul 18 '14 at 10:59
  • 6
    Please add a remark that diference between `remote_file` and `file` is also that `file` is executed at **compile** time, while `remote_file` is executed at **run** time. So when you need to work with files at run time - `remote_file`is a good work around. – Cherry Dec 20 '14 at 06:21
  • 3
    Definitely note that for binary files you'll need to change it to `IO.read(curr_path, mode: 'rb')` or risk corrupting the destination file. – TheLonelyGhost Mar 31 '17 at 21:17
  • 1
    **remote_file** did the trick for me as it allowed me to keep track of file deltas automatically. Thanks! – hmacias Apr 11 '19 at 14:21
15

I got it working by using bash resource as below:

bash "install_jettyhightide" do
  code <<-EOL
  unzip /var/tmp/jetty-hightide-7.4.5.v20110725.zip -d /opt/jetty/
  mv /opt/jetty/jetty-hightide-7.4.5.v20110725/* /opt/jetty/
  cp /opt/jetty/bin/jetty.sh /etc/init.d/jetty
  update-rc.d jetty defaults
  EOL
end

But I was really hoping for a chef way of doing it. copying/moving files locally would be the most generic task a sysadmin will need to do.

slayedbylucifer
  • 22,878
  • 16
  • 94
  • 123
  • 1
    The 'chef' way of doing things would look more like the "For-each directory" example of @CAMOBAP's example -- spawn a resource for every directory and every file. That is actually terrible for performance. What you did is actually correct, and is the kind of use case that the bash resource is there for. You could add a `not_if` or `creates` to check for /etc/init.d/jetty/jetty.sh being created to make it more idempotent. – lamont Sep 01 '14 at 19:34
  • 1
    NOTE: there is now the [archive_file](https://docs.chef.io/resources/archive_file/) resource in chef-client which is better than most of these roll-your-own-execute-resource approaches – lamont Aug 21 '20 at 01:34
15

I know this question have already been answered, and discussed, but here is the method I use when creating files.

  1. First include the file under the cookbook's files/default folder
  2. Then on your recipe use the cookbook_file resource

e.g:

cookbook_file "/server/path/to/file.ext" do
  source "filename.ext"
  owner "root"
  group "root"
  mode 00600
  action :create_if_missing
end

From chef documentation: https://docs.chef.io/resources/cookbook_file/

The cookbook_file resource is used to transfer files from a sub-directory of the files/ directory in a cookbook to a specified path that is located on the host running the chef-client or chef-solo.

Peter Vandivier
  • 606
  • 1
  • 9
  • 31
Marcos Abreu
  • 2,832
  • 22
  • 18
  • 4
    This post is about moving files already on the target machine to another location on the target machine. cookbook_file cannot do that. cookbook_file is used to copy files from the cookbook to the target machine, not locally around the target machine. – Martin Nov 26 '14 at 22:18
  • I do some action with use `copy` module on **Ansible**, thank you. – Chu-Siang Lai Oct 04 '16 at 03:51
9

You could give the ark cookbook a try. This extracts the file for you and you afterwards notice an execute resource.

StephenKing
  • 36,187
  • 11
  • 83
  • 112
  • +1 I can highly recommend this cookbook. Simplifies he handling of archive files. Also supports source installs. – Mark O'Connor Oct 08 '13 at 20:29
  • Yes, I knew about ark. but that would be an overkill to use it just for copy/move. My main intention is to copy/move files and not installing them vi .tar/.zip archive. I really wonder why opscode did not consider creating a chef-way of copy/move files. – slayedbylucifer Oct 09 '13 at 04:41
  • Yes, I somehow agree. But they can't start a product with support for everyting. Ark is also fine for simple unpackaging, even with out the ./configure && make && make install dance. – StephenKing Oct 09 '13 at 12:57
  • There is now an [archive_file](https://docs.chef.io/resources/archive_file/) resource built into chef-client. – lamont Aug 21 '20 at 01:36
4

Besides the way you've done it and accepted it, if you only wanted to run one command like you initially asked (copy or move), and not run a block of commands, then you could do it with the execute resource:

execute "copy_core" do
    command "mv /var/tmp/jetty-hightide-7.4.5.v20110725 /opt/jetty"
    user "root"
end

Maybe this will help someone else looking at this in the future.

Titi
  • 1,126
  • 1
  • 10
  • 18
  • 2
    Warning here: this will run the mv again and again at each chef run if no guard is added, if a file in target has been modified, any change will be lost. – Tensibai Sep 14 '15 at 16:16
  • @Tensibai, he just asked how to perform the 'mv' command in Chef. It all depends on where you put this block. You can always add `not_if { File.exist?("/opt/jetty") }` in the block so it doesn't redo it if it's already been copied. – Titi Sep 14 '15 at 16:23
  • 1
    The warning is for future readers of this question/answers. (And solving a XY problem is not always the best thing to do) – Tensibai Sep 14 '15 at 16:56
2

I would actually use something like the following (notice "binread") as this would work for text files and binary files. using "read" would yield surprising results with binary files particularly if you use both unix and windows systems.

file destination do
  content IO.binread(source)
  action  :create
end
rui
  • 159
  • 6
  • This is actually quite important, working with stuff like Java Keystore files will break if you use the default read method. – Serban Cezar May 22 '20 at 09:34
0

If your recipe is already tied to Windows, you can use embedded PowerShell scripts, like this:

# Copy files from "C:/foo/lib" to "C:/foo"
powershell_script "copy_lib" do
  code <<-EOH
    $ErrorActionPreference = "Stop"
      Get-ChildItem -Path "C:/foo/lib" -File | Foreach-Object {
        Copy-Item -Path $_.Fullname -Destination "C:/foo" -Force
      }
  EOH
end

# Delete "C:/foo/lib" folder
powershell_script "delete_lib" do
  code <<-EOH
    $ErrorActionPreference = "Stop"
    Remove-Item -Path "C:/foo/lib" -Recurse
  EOH
end
JoelFan
  • 37,465
  • 35
  • 132
  • 205
-1

To Copy files locally in CHEF

file "C:/Users/Administrator/chef/1.xml" 

do         --->       tar content lazy 

{

IO.read("C:/Users/Administrator/chef-repo/cookbooks/2.xml")

} -->src

action :create

end
Jasper
  • 7,031
  • 3
  • 35
  • 43