Seongsiks

Being A DevOpser. Powered by
Obtvse, highlight.js, theme toc Creative Commons License
Seongsiks Twitter Github Email
DevOps Ruby On Rails Chef Projects Misc Movies & Drama ME

Chef Cookbook만들기 #2

Overview

앞 포스트에서 다루지 않은 Provider. Recipe에서 resource를 만들고, resource에서 action을 실행시키는데, 이 action이 실제로 정의 되어 있는 곳이 바로 Provider 입니다. 이번 포스트에서 실제로 install이라는 액션을 명령했을때 수행되는 부분인 provider를 살펴 보도록 하겠습니다.

Code

#
# Author:: Bryan W. Berry (<bryan.berry@gmail.com>)
# Cookbook Name:: java
# Provider:: ark
#
# Copyright 2011, Bryan w. Berry
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

def parse_app_dir_name url
  file_name = url.split('/')[-1]
  # funky logic to parse oracle's non-standard naming convention
  # 여기서도 뭐라고 하는군요.. 오라클에서 네이밍 컨벤션이 없는것을요.. 
  # 이렇게 이름이 지멋대로인 자바 소스 파일에서 버전 정보를 가져오기 위해서 다음과 같이 이상한 짓을 해야합니다.
  # for jdk1.6
  if file_name =~ /^(jre|jdk).*$/
    major_num = file_name.scan(/\d/)[0]
    update_num = file_name.scan(/\d+/)[1]
    # pad a single digit number with a zero
    if update_num.length < 2
      update_num = "0" + update_num
    end
  else
    app_dir_name = file_name.split(/(.tar.gz|.zip)/)[0]
    app_dir_name = app_dir_name.split("-bin")[0]
  end
  [app_dir_name, file_name]
end

# 여기가 바로 install이라는 액션이 실제로 정의 되는 부분입니다. 
action :install do
  app_dir_name, tarball_name = parse_app_dir_name(new_resource.url)
  app_root = new_resource.app_home.split('/')[0..-2].join('/')

  app_dir = "#{app_root}/#{app_dir_name}"

  unless new_resource.default
    Chef::Log.debug("processing alternate jdk")
    app_dir = app_dir  + "_alt"
    app_home = app_dir
  else
    app_home = new_resource.app_home
  end

  unless ::File.exists?(app_dir)
    Chef::Log.info "Adding #{new_resource.name} to #{app_dir}"
    require 'fileutils'

    unless ::File.exists?(app_root)
      FileUtils.mkdir_p app_root, :mode => new_resource.app_home_mode
      FileUtils.chown new_resource.owner, new_resource.owner, app_root
    end

    #=====================================================================#
    # remote_file은 chef recipe에서 굉장히 중요한 개념으로 chef에서 기본적으로 제공하는 
    # provider resource중에 하나입니다. resource라는 개념이 새로 나오지만, 단순하게 라이브러
    # 리라고 생각하면 될듯 합니다. 일단은요. remote_file은 chef workstation이나 다른 경로에 
    # 있는 파일을 타겟 노드에 전송할때 많이 사용됩니다. 자세한 사항은 
    # http://docs.opscode.com/resource_remote_file.html 
    #=====================================================================#
    r = remote_file "#{Chef::Config[:file_cache_path]}/#{tarball_name}" do
      source new_resource.url
      #checksum new_resource.checksum
      mode 0755
      action :nothing
    end
    r.run_action(:create_if_missing)

    require 'tmpdir'

    tmpdir = Dir.mktmpdir


    # 라이브러리 파일의 종류에 따라 압축을 푸는 방식이 다르게 처리해줍니다. 
    case tarball_name
    when /^.*\.bin/
      cmd = Mixlib::ShellOut.new(
                                [ "cd #{tmpdir};",
                                   "cp #{Chef::Config[:file_cache_path]}/#{tarball_name} . ;",
                                   "bash ./#{tarball_name} -noregister"
                                 ] ).run_command
      unless cmd.exitstatus == 0
        Chef::Application.fatal!("Failed to extract file #{tarball_name}!")
      end
    when /^.*\.zip/
      cmd = Mixlib::ShellOut.new("unzip #{Chef::Config[:file_cache_path]}/#{tarball_name} -d #{tmpdir}").run_command
      unless cmd.exitstatus == 0
        Chef::Application.fatal!("Failed to extract file #{tarball_name}!")
      end
    when /^.*\.tar.gz/
      cmd = Mixlib::ShellOut.new(
                              "tar xvzf #{Chef::Config[:file_cache_path]}/#{tarball_name} -C #{tmpdir}"
                               ).run_command
      unless cmd.exitstatus == 0
        Chef::Application.fatal!("Failed to extract file #{Chef::Config[:file_cache_path]}/#{tarball_name}!")
      end
    end

    # 압축 푼것을 설치하고자 하는 경로로 카피하고
    app_extracted_name = Dir.glob("#{tmpdir}/*")[0].split("/")[-1]
    Chef::Log.debug( "mv #{tmpdir}/#{app_extracted_name} #{app_dir}")
    cmd = Mixlib::ShellOut.new(
                              "cp -r #{tmpdir}/#{app_extracted_name} #{app_dir}"
                             ).run_command
    unless cmd.exitstatus == 0
        Chef::Application.fatal!("Command \' cp #{tmpdir}/#{app_extracted_name} #{app_dir} \' failed")
    end

   # 임시파일을 삭제합니다^^
    FileUtils.rm_r tmpdir
    new_resource.updated_by_last_action(true)

    #update-alternatives
    Chef::Log.debug ["app_home is #{app_home} and app_dir is #{app_dir}, app_root is #{app_root}"]
    FileUtils.ln_sf "#{app_dir}#{app_extracted_name}", app_home
  end
end

# remove 액션은 자바 홈디렉토리를 삭제해버리는 겁니다. 
action :remove do
  app_dir_name, tarball_name = parse_app_dir_name(new_resource.url)
  app_root = new_resource.app_home.split('/')[0..-2].join('/')
  app_dir = app_root + '/' + app_dir_name

  if ::File.exists?(app_dir)
    new_resource.bin_cmds.each do |cmd|
      cmd = execute "update_alternatives" do
        command "update-alternatives --remove #{cmd} #{app_dir} "
        returns [0,2]
        action :nothing
      end
      cmd.run_action(:run)
    end
    Chef::Log.info "Removing #{new_resource.name} at #{app_dir}"
    FileUtils.rm_rf app_dir
    new_resource.updated_by_last_action(true)
  end
end

결론

provider에서 중요한것은 제가 생각했을때 저 케이스 문들인것 같습니다. 여러가지 경우를 다 고려해서 각 케이스별로 대응 하는것이죠. 실제 현업에서는 이렇게 범용으로 만들지는 않을 수 있겠으나, 오픈 소스에서는 이렇게 여러가지 사항들을 다 고려해야죠.

comments powered by Disqus
Back to Chef