end0tknr's kipple - web写経開発

太宰府天満宮の狛犬って、妙にカワイイ

サーバにinstallされているOSやMWの一覧を自動収集したい (centos版)

以下のように perl script書いて、実行したものを xymonあたりで収集すれば、OKな気がします

#!/usr/local/bin/perl
use strict;
use warnings;
use Data::Dumper;

my $OS_INFO_CMD = 'cat /etc/system-release';
my $RPM_LIST_CMD = 'rpm -aq';  # = yum list installed
my @SRC_LIST_CMDS =
    (["mysql",              "/usr/local/mysql/bin/mysql --version"],
     ["perl",               "/usr/local/bin/perl -v"],
     ["java",               "/usr/bin/java -version"],
     ["apache httpd",       "/home/end0tknr/local/apache24/bin/httpd -v"]);

main();
exit(0);

sub main {

    my @sw_list;
    my $os_info  = installed_os_info();
    push(@sw_list,$os_info);
    my $src_list = installed_src_list();
    push(@sw_list,@$src_list);
    my $rpm_list = installed_rpm_list();
    push(@sw_list,@$rpm_list);

    for my $sw_info ( @sw_list ){
        print join("\t",@$sw_info),"\n";
    }
}


sub installed_os_info {

    my $fh;

    my $cmd = $OS_INFO_CMD;
    open($fh,"-|", $cmd) or die "fail open $cmd $!";
    my ($line) =<$fh>;
    close($fh) or die "fail close $cmd $!";

    chomp($line);
    return ["OS",$line];
}

sub installed_src_list {
    
    my @ret;
    for my $cmd ( @SRC_LIST_CMDS ) {
        my $fh;
        open($fh,"-|", "$cmd->[1] 2>&1") or die "fail open $cmd->[1] $!";
        
        my $installed_info = "";
        for my $line ( <$fh> ){
            chomp($line);
            if($line){
                $installed_info = $line;
                push(@ret,["MW",$cmd->[0],$installed_info]);
                last;
            }
        }
        unless( $installed_info ){
            die "fail find installed info $cmd->[1] $!";
        }
        close($fh) or die "fail close $cmd $!";
    }
    return \@ret;
}

sub installed_rpm_list {

    my $fh;
    open($fh,"-|", $RPM_LIST_CMD) or die "fail open $RPM_LIST_CMD $!";

    my @ret;
    for my $line ( sort <$fh> ){
        chomp($line);
        push(@ret,["MW",$line]);
    }
    
    close($fh) or die "fail close $RPM_LIST_CMD $!";

    return \@ret;
}

1;
$ ./get_inventory.pl 
OS  CentOS Linux release 7.4.1708 (Core) 
MW  mysql   /usr/local/mysql/bin/mysql  Ver 14.14 Distrib 5.6.40, for Linux (x86_64) using  EditLine wrapper
MW  perl    This is perl 5, version 26, subversion 2 (v5.26.2) built for x86_64-linux-thread-multi
MW  java    openjdk version "1.8.0_161"
MW  apache httpd    Server version: Apache/2.4.34 (Unix)
MW  GeoIP-1.5.0-11.el7.x86_64
MW  ImageMagick-6.7.8.9-15.el7_2.x86_64
MW  ImageMagick-devel-6.7.8.9-15.el7_2.x86_64
MW  NetworkManager-1.8.0-9.el7.x86_64
MW  NetworkManager-libnm-1.8.0-9.el7.x86_64
MW  NetworkManager-team-1.8.0-9.el7.x86_64
MW  NetworkManager-tui-1.8.0-9.el7.x86_64
MW  NetworkManager-wifi-1.8.0-9.el7.x86_64
MW  OpenEXR-libs-1.7.1-7.el7.x86_64
MW  acl-2.2.51-12.el7.x86_64
MW  aether-api-1.13.1-13.el7.noarch
<略>
MW  yum-3.4.3-154.el7.centos.noarch
MW  yum-metadata-parser-1.1.4-10.el7.x86_64
MW  yum-plugin-fastestmirror-1.1.31-42.el7.noarch
MW  zip-3.0-11.el7.x86_64
MW  zlib-1.2.7-17.el7.x86_64
MW  zlib-devel-1.2.7-17.el7.x86_64
$ 

IT資産管理(CMDB)のGLPIをinstall

https://glpi-project.org/

https://it.hirokun.net/entry/amazonlinux-glpi943

GLPI自体は、phpな tar.gz を解答し、配備するだけですが、 必要なライブラリ群をsrcからinstallしていたら、どうも上手くいかないので、 上記urlを参考にさせて頂きながら、パッケージ(yum)でinstall。

以下は、雑多なメモ

リポジトリ追加

$ sudo yum install epel-release
$ rpm -Uvh https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm
$ rpm -Uvh http://rpms.famillecollet.com/enterprise/remi-release-7.rpm

php関連 install

$ sudo yum install php70-pecl-apcu php70-xmlrpc php70-ldap php70-imap php70-opcache

$ sudo yum install php70
$ sudo yum install php70-php-pecl-apcu
$ sudo yum install php70-php-xmlrpc
$ sudo yum install php70-php-ldap
$ sudo yum install php70-php-imap
$ sudo yum install php70-php-opcache
$ sudo yum install php70-php-mbstring
$ sudo yum install php70-php-gd
$ sudo yum install php70-php-mysqlnd
$ sudo yum install php-pear-CAS

GLPIに最初からあるユーザ

  • 管理者アカウント:glpi/glpi
  • 技術者アカウント:tech/tech
  • 標準アカウント: normal/normal
  • 投稿専用アカウント: post-only/postonly

REST API

$ curl -X GET \
-H 'Content-Type: application/json' \
-H "Authorization: Basic Z2xwaTpnbHBp" \
-H "App-Token: f7g3csp8mgatg5ebc5elnazakw20i9fyev1qopya7" \
'http://path/to/glpi/apirest.php/initSession'

↑こちらのbasic認証は私の環境で動作しましたが、 以下の user_token 認証は動作しない。。。。

$ curl -X GET \
-H 'Content-Type: application/json' \
-H "Authorization: user_token q56hqkniwot8wntb3z1qarka5atf365taaa2uyjrn" \
-H "App-Token: f7g3csp8mgatg5ebc5elnazakw20i9fyev1qopya7" \
'http://path/to/glpi/apirest.php/initSession```

apache httpd において、ldap basic認証 + グループファイル認証 (AuthGroupFile)

するには、httpd.confを次のようにすれば、OKみたい。

<Location /group-only
   AuthType Basic
   AuthBasicProvider ldap
   SetHandler ldap-status
   AuthGroupFile /etc/httpd/passwd/groups
   AuthName "Sexy&Password"
   AuthLDAPURL ldap://ldap.example.co.jp/ou=people,o=sexy-group?uid
   require group sysfur
</Location>
sysfur: user1 user2 ... usern

MS ACCESSでのデータ入力時に、外部参照テーブルの値で補完 - ルックアップ機能

先程のエントリに関連するかも知れませんが、 MS ACCESSでのデータ入力時に、外部参照テーブルの値で補完したい場合、 デザインビューを開いて、ルックアップを設定すれば、OK

ポイントは、以下の赤線部分。

f:id:end0tknr:20190310172626p:plain

mysql の外部キー制約設定(FOREIGN KEY)でエラーなら、「SHOW ENGINE INNODB STATUS」でログを見ましょう

mysql> ALTER TABLE software ADD CONSTRAINT software_hw_name
    -> FOREIGN KEY (fk_hw_name) REFERENCES hardware (hw_name)
    -> ON DELETE SET NULL ON UPDATE CASCADE;
ERROR 1215 (HY000): Cannot add foreign key constraint

上記のようなエラーに出会ったら、ググるより、 「SHOW ENGINE INNODB STATUS」でmysqlのログを見る方が解決への近道です。

mysql> SHOW ENGINE INNODB STATUS\G
<略>
------------------------
LATEST FOREIGN KEY ERROR
------------------------
2019-03-08 10:01:35 7fe1f6294700 Error in foreign key constraint of table it_assets/#sql-cfa_4e4:

FOREIGN KEY (fk_hw_name) REFERENCES hardware (hw_name)
ON DELETE SET NULL ON UPDATE CASCADE:
Cannot find an index in the referenced table where the
referenced columns appear as the first columns, or column types
in the table and the referenced table do not match for constraint.
Note that the internal storage type of ENUM and SET changed in
tables created with >= InnoDB-4.1.12, and such columns in old tables
cannot be referenced by such columns in new tables.
See http://dev.mysql.com/doc/refman/5.6/en/innodb-foreign-key-constraints.html
for correct foreign key definition.

今回の場合、create indexを追加することで解消しています

re: エンジニアリング組織論への招待 ~ 不確実性に向き合う思考と組織のリファクタリング

gihyo.jp

読んでみた。「ブクログ大賞[2018] ビジネス書部門大賞」らしい。

分かりやすい。「上流工程はウォーターフォールよりスクラムかな?」と思った。

以下、自分なりのポイントを記載

「不確実性」とは、「未来」と「他人」から構成

                  不確実を確実にするものが情報
┌──┐        ┌────┐    :     ┌───┐
│不安│←───│不確実性│────→│確実性│
└──┘        └┬───┘          └───┘
  │              │  ┌───┐
  │              ├─┤未  来│─対策→ 行動と観察
  │              │  └┬──┘
  │              │    ├ 方法の不確実性
  │              │    └ 目的の不確実性
  │              │(他人が未来の不確実を増大させる)  
  │              │  ┌───┐
  └──────→└─┤他  人│─対策→ コミュニケーション
(不安が他人の不確実を └┬──┘
 増大させる)            ├ 他者理解の不確実性-人は他人や事象を完全には理解不可
                        ├ 伝達の不確実性 -   コミュニケーションが到達するとは限らない 
                        └ 成果の不確実性 -   理解されても行動するとは限らない

経験主義と仮設主義、設計主義

主義 説明
経験主義 情報を入手する為、まず、行動し、その結果観察から問題解決
仮説主義 限定された情報から全体像を想定し、それを確かめることで問題解決
設計主義 ウォーターフォール型。範囲後はスクラム型(経験主義)

不確実性コーンに対するプロとアマの取り組み順

プロ

不確実性の高い部分から取り組み、短い時間で一定の品質まで作成し、 残りの時間で細かい部分を作りこむ。(早い段階で、全体像が見える)

アマ

不確実性の低い部分から取り組む為、最終工程まで全体像が見えない。 (重要部分を後回しにしている為、終盤での問題発生時にリカバリー難)

品                    ┏━━━━
質            ┏━━━┛        │
↑            ┃↑プロ          │
│      ┏━━┛                │
│      ┃                      │
│      ┃                  ┌─┘
│  ┏━┛                  │
│  ┃                ┌──┘
│  ┃      ↓アマ│
│  ┃        ┌───┘
│    ────┘
└───────────────→時間

観察できないものは制御できない

一見、困難そうな問題であっても、 制御や観察できる部分と、それ以外に切り分け、制御や観察できる部分から取り組む。

アジャイル開発宣言 (従来との違い)

従来重視 アジャイルで重視
プロセスやツール 個人と対話
包括的なドキュメント 動くソフト
契約交渉 顧客との交渉
計画順守 変化への対応

その他

共感と同感の違い

私が「理解されたか」と「合意できたか」を使い分けていた点と同じですね。

事実と意見の違い

心理的安全

not パワハラ な状態が生産性を上げる

構成管理データベース(CMDB)

IT資産(ハードウェアやソフトウェア)を管理するDB table構成を考える - end0tknr's kipple - 新web写経開発

以前、上記のエントリを記載していますが、 世の中(ITIL)的には、構成管理データベース(CMDB)というものがあるらしい。

構成管理データベース(CMDB)の構築/改善 | アシスト

例えば、商用であれば、アシスト社の↑こういうものや OSSであれば、CMDBuild ↓ というプロダクトがあるそうです。

https://www.ospn.jp/osc2015.enterprise/pdf/osc2015.enterprise_cmdbuild_usergroup.pdf

以下、2019/2/28追記

インベントリ収集

  • open audit
  • OCS Inventory NG

構成管理DB

  • CMDBuild
  • GLPI
  • snipe-it

(再) バーバラ・ミント - 考える技術・書く技術 - 問題解決力を伸ばすピラミッド原則

https://end0tknr.hateblo.jp/entry/20180422/1524338872

以前のエントリの再掲載。

「ピラミッド原則」を理解したつもりが、 重要な 「導入部」を考えず、いきなり本題に入ってしまう為、再確認。

「導入部」で読み手をしっかり、チェックインさせ、「ピラミッド原則」で論じよ が重要

「導入部」とは

  • 「導入部」を考えることを省略しない (各章に書くこともOK)
  • 「導入部」は、読み手に思い起こさせるもの (知識を与えるものではない)
  • 「導入部」は、読み手が合意する事項に限定
  • 「導入部」は、本文中で述べるメッセージと混ぜない(本文自体が複雑になる)
  • 過去の出来事は、常に「導入部」に記述

「導入部」の構成 -「S(状況)」「C(複雑化)」「Q(疑問)」

導入部は「状況(Situation)」→「複雑化(Complication)」→「疑問(Question)」で 展開される。

また、ほとんどの文書は4つの疑問のどれかに答える

S:状況
(主題に関し
確認されている事実)
C:複雑化
(次に起こった
疑問へつながる事実)
Q:疑問
しなければならない仕事がある その仕事の妨げに
なることが起きた
どうすればよいか
問題がある 解決方法を知っている 解決方法を実行するには
どうすればよいか
問題がある 解決方法が提案された それは正しい解決方法か
行動をとった その行動は効果がなかった なぜ効果がなかったか

導入部の「状況(Situation)」の構成

導入部の「状況(Situation)」は、更に 「オープニング/スタートポイント」+「懸念される出来事」から構成。

ここで「オープニング/スタートポイント」とは、 読み手にスムーズに入ってもらう為の事前情報や背景。

例えば、「ある食品会社は30年以上、同じやり方で +10%/年の成長を続けてきた」

その他のオープニングの例としては以下がある。

グループ 内容
構造 組織図、システム構成図、 工場の配置図、市場分布図
プロセス 販売活動、事務処理、流通システム、生産工程

また、「懸念される出来事」とは「望ましくない結果(R1)をもたらすもの」

グループ 内容
外的引き金 新たな競合、新技術への転換、政府方針や環境の変化
内的引き金 新市場へ進出、製造ラインを変更、プロセスを増やした
新しい認識 製品/プロセスの能力低下、顧客変化を意味する調査結果

「導入部」の例

※以下のR1, R2は、それぞれ 「今の何が好ましくないか (R1)」「代わりに何を望んでいるか(R3)」。

NO.1 我々は何をすべきか?

グループ 内容
S(状況) 状況
C(複雑化) R1, R2
Q(疑問) どうやって、R1→R2へ行くか?
グループ
S(状況) 現在、市場Aに対し、Xのアプローチを取っている
C(複雑化) 市場は高い成長率にある為、Xのアプローチでは限界がある
Q(疑問) どのように変えるべきか?
          ┌────────┐
          │このようにせよ  │
          └────┬───┘
      ┌──────┼──────┐How?
┌──┴──┐┌──┴──┐┌──┴──┐
│ステップ1 ││ステップ2 ││ステップ3 │
└─────┘└─────┘└─────┘

NO.2 今やろうとしていることをやるべきか?

グループ 内容
S(状況) 状況, R1, R2
C(複雑化) 解決策
Q(疑問) 正しい解決策か? どう実行すべきか?
グループ
S(状況) 問題となるかもしれない.なぜなら新しい手法が業界で始まろうとしている為
C(複雑化) もし、そうならやり方を変える必要がある
Q(疑問) そのようにすべきか
          ┌────────┐
          │そのとおり      │
          └────┬───┘
      ┌──────┼──────┐How?
┌──┴──┐┌──┴──┐┌──┴──┐
│Aを変えよ ││Bを変えよ ││Cを変えよ │
└─────┘└─────┘└─────┘

NO.5 どの選択肢を選ぶべきか?

グループ 内容
S(状況) 状況, R1, R2
C(複雑化) 問題解決の選択肢がある
Q(疑問) どの選択肢がベストか?
          ┌────────┐
          │Aがよい         │
          └────┬───┘
      ┌──────┼──────┐Why?
┌──┴──┐┌──┴──┐┌──┴──┐
│Xの点で...││Yの点で...││Zの点で...│
└─────┘└─────┘└─────┘
  • ※注意 * 選択肢は、常に導入句の「複雑化」の部分に記載。 なぜなら、選択肢が読み手にとって既知でない場合、持ち込むべきでない為。

例:「我々には問題を解決する3つの方法がある」

NO.6 我々の戦略は、いかなるものであるべきか?

グループ 内容
S(状況) 状況, R1
C(複雑化) 変えないとは分かっているが、どの方向へ、どう到達すべきか不明
Q(疑問) 我々の目的や戦略は、どうあるべきか?
グループ
S(状況) 大市場で小さなプレーヤーとして活動している
C(複雑化) 潜在機会がどの程度のものか不明。しかし、我々が得ているのは市場の一部
Q(疑問) 我々の目的や戦略は、どうあるべきか?
          ┌────────────┐
          │ニッチ市場Aで抜きん出よ │←─────┐Why?
          └────────────┘            │
                                                  │
┌─────────┐ ┌───────┐  ┌──┴─────┐
│実際に魅力的なのは├→│御社にとって、├→│市場Aへの特化に │
│2~3セグメント    │ │進出方法は限定│  │最大のチャンス  │
└─────────┘ └───────┘  └────────┘

キーラインは、上記の演繹的論理、または戦略を実行に移す為のステップ

「導入部」を考えることを省略しない (各章に書くこともOK)

つまり、文章の見出し?構成としては、以下

        【タイトル】
全体の導入句
  S(状況)。(Sにはオープニングも記載)
  C(複雑化)と疑問。
  
(ここに目次でも良い気がします)

【1】キーライン1
キーライン1の導入句

【1.1】STEP1
STEP1の導入句
【1.2】STEP2
STEP2の導入句
【1.3】STEP3
STEP3の導入句

【2】キーライン2
キーライン2の導入句

【2.1】Aの切り口
Aの導入句
【2.2】Bの切り口
Bの導入句
【3.3】Cの切り口
Cの導入句

その他の注意点

  • 膨大なデータがあり、確信を持つ為、「全てのデータを追いかけ」「あれもこれも解決策として打ち出す」

  • 全体を締めくくるにあたり、既に述べたことを繰り返すことは行ってはならない。(理論的には、適切な導入部を設け、文書全体がピラミッド構造であれば結論は不要の為)

convert unix time to windows nt time

SMB for perl であるファイルのタイムスタンプをみると unix time にしては、妙に長い文字列。

どうやら、windows nt time という時刻表示らしく、 SMB::Time for perl において、その相互変換がある

以下、 https://metacpan.org/pod/SMB::Time の抜粋

my $nttime_factor = 10_000_000;
my $nttime_offset = 11_644_473_600;

sub from_nttime ($) {
    my $nttime = shift || return 0;
    return $nttime / $nttime_factor - $nttime_offset;
}

sub to_nttime ($) {
    my $time = shift || return 0;
    return ($time + $nttime_offset) * $nttime_factor;
}

shell scriptとバイナリファイルを1つのファイルにまとめる

https://github.com/Microsoft/OMS-Agent-for-Linux/releases

にある omsagent-?.?.?-256.universal.x86.sh は、shell scriptなのに ファイルサイズが大きいので、中身を読んでみたら shell script にバイナリファイル(*.tar)をまとめていた。

多分、バイナリファイルを cat し、 shell script へマージしたのかと思います。

使用する際は、自信のscriptを tail していましたが、このアイデアは、私にはなかった...

aws s3にあるファイルをバージョンID付きで削除 - perl

aws s3には、バージョニングという機能があり、 単純にファイル削除した場合、実体は削除されず、 削除フラグ=ON(つまり、論理削除)の状態になるらしい。

物理削除したい場合、バージョンID付きで削除すればよいらしく、perlで書くと、以下の通り。

  • Net::Amazon::S3 * を使用したかったのですが、 Net::Amazon::S3 によるバージョンID付き削除方法が分からなかった為、 次のように素朴なperl scriptにしました。
#!/usr/local/perl/bin/perl
use strict;
use warnings;
use utf8;
use Digest::HMAC_SHA1;
use File::Basename;
use File::stat;
use HTTP::Date;
use HTTP::Request;
use HTTP::Request::Common;
use LWP::UserAgent;
use Log::Log4perl;
use Data::Dumper;

# https://docs.aws.amazon.com/ja_jp/AmazonS3/latest/dev/RESTAuthentication.html
# http://blog.yusuke.be/entry/2014/01/23/011321
my $CONF =
    {aws_s3=>
     {host=>                   'ないしょ',
      aws_access_key_id =>     'ないしょ',
      aws_secret_access_key => 'ないしょ',
      bucket=>                 'ないしょ'},
     chunk_size_limit => 1000,
     chunk_interval   => 10,  #sec
     log=>
     {'log4perl.rootLogger'=> 'DEBUG, LOGFILE, CONSOLE',
      'log4perl.appender.LOGFILE'=>'Log::Log4perl::Appender::File',
      'log4perl.appender.LOGFILE.dir'=>'/home/end0tknr/delete/',
      'log4perl.appender.LOGFILE.filename'=>
      '/home/end0tknr/delete/delete.log',
      'log4perl.appender.LOGFILE.mode'=>'append',
      'log4perl.appender.LOGFILE.layout'=>'Log::Log4perl::Layout::PatternLayout',
      'log4perl.appender.LOGFILE.layout.ConversionPattern'=>'%d [%p] %m %n',

      'log4perl.appender.CONSOLE'=> 'Log::Log4perl::Appender::Screen',
      'log4perl.appender.CONSOLE.layout' => 'Log::Log4perl::Layout::PatternLayout',
      'log4perl.appender.CONSOLE.layout.ConversionPattern' => '%d [%p] %m %n'}
     };
my $LOGGER;


main(@ARGV);

sub main {
    my (@del_files_lists) = @_;

    for my $del_files_list ( @del_files_lists ){
        # $del_files_list のfileには削除対象のobj keyを改行区切りで記載
        $LOGGER = init_logger($del_files_list);

        # 各削除file一覧は、chunk_size_limit 毎に分割して読込みます
        my $del_files_chunks = load_del_files_list($del_files_list);

        my $i = 0;
        for my $del_files_chunk ( @$del_files_chunks ){
            my $j = 0;
            $i++;
            for my $obj_key_org ( @$del_files_chunk ){
                next unless $obj_key_org;
                
                $j++;
                $LOGGER->info("$i $j $obj_key_org");
                
                # url encoding
                my $obj_key = $obj_key_org;
                $obj_key =~ s/([^ 0-9a-zA-Z])/"%".uc(unpack("H2",$1))/eg;
                
                # version id取得
                my $verion_id = get_version_id($obj_key);
                if( $verion_id ){
                    $LOGGER->info("get_version_id($obj_key_org) -> $verion_id");
                } else {
                    $LOGGER->error("fail get_version_id($obj_key_org)");
                    next;
                }

            }

            sleep( $CONF->{chunk_interval} );

            last;  # for debug
        }
    }
}

sub load_del_files_list {
    my ($del_files_list) = @_;

    open my $fh,"<", $del_files_list or
        die "fail open $del_files_list $!";
    my $ret = [];
    my $chunk_no = 0;
    my $chunk_size = 0;
    for my $line ( <$fh> ){
        chomp($line);
        if( not defined($ret->[$chunk_no]) ){
            $ret->[$chunk_no] = [];
        }
        if($chunk_size < $CONF->{chunk_size_limit}){
            push(@{$ret->[$chunk_no]}, $line);
            $chunk_size++;
        } else {
            $chunk_size = 0;
            $chunk_no++;
        }
    }
    close($fh);

    return $ret;
}


sub del_s3_obj {
    my ($obj_key, $verion_id) = @_;

    $verion_id = '' unless $verion_id;
    
    my $url = join('/',
                   "http://$CONF->{aws_s3}->{host}",
                   "$CONF->{aws_s3}->{bucket}",
                   $obj_key);
   $url .= "?versionId=$verion_id";

    my $date_str = time2str();
    my $str_to_sign_org =
        join("\n",
             'DELETE',
             '',
             '',
             $date_str,
             "/$CONF->{aws_s3}->{bucket}/$obj_key");
    $str_to_sign_org .= "?versionId=$verion_id";

    my $hmac = Digest::HMAC_SHA1->new($CONF->{aws_s3}->{aws_secret_access_key});
    $hmac->add($str_to_sign_org);
    my $sig = $hmac->b64digest . '=';
    
    my $header =
        [
         'Host' => $CONF->{aws_s3}->{host},
         'Date'=> $date_str,
         'Authorization'=>
         "AWS $CONF->{aws_s3}->{aws_access_key_id}:$sig"];
    
    my $req = HTTP::Request->new('DELETE', $url, $header);
    my $ua = LWP::UserAgent->new;
    my $res = $ua->request($req);

    if(not $res->is_success) {
        $LOGGER->error( $res->status_line );
        $LOGGER->error( Dumper($res) );
        return;
    }
    return $obj_key;
}


sub get_version_id {
    my ($obj_key) = @_;
    
    my $url = join('/',
                   "http://$CONF->{aws_s3}->{host}",
                   "$CONF->{aws_s3}->{bucket}",
                   $obj_key);
    my $date_str = time2str();
    my $str_to_sign_org =
        join("\n",
             'HEAD',
             '',
             '',
             $date_str,
             "/$CONF->{aws_s3}->{bucket}/$obj_key");

    my $hmac = Digest::HMAC_SHA1->new($CONF->{aws_s3}->{aws_secret_access_key});
    $hmac->add($str_to_sign_org);
    my $sig = $hmac->b64digest . '=';
    
    my $header =
        [
         'Host' => $CONF->{aws_s3}->{host},
         'Date'=> $date_str,
         'Authorization'=>
         "AWS $CONF->{aws_s3}->{aws_access_key_id}:$sig"];
    
    my $req = HTTP::Request->new('HEAD', $url, $header);
    my $ua = LWP::UserAgent->new;
    my $res = $ua->request($req);

    return $res->header('x-amz-version-id');
}

sub init_logger {
    my ($org_filepaths_list) = @_;

    if ($org_filepaths_list){
        my($filename, $dirs) = File::Basename::fileparse($org_filepaths_list);
        my $datetime = unixtime_to_str(time);
        $CONF->{log}->{'log4perl.appender.LOGFILE.filename'} =
            $CONF->{log}->{'log4perl.appender.LOGFILE.dir'} ."/".
            join(".",$filename,$datetime,"log");
    }
    
    Log::Log4perl::init($CONF->{log});
    my $logger = Log::Log4perl::get_logger("rootLogger");
    unless($logger){
        die "fail init_logger() $!";
    }
    return $logger;
}

sub unixtime_to_str {
    my ($unix_time) = @_;

    my ($sec,$min,$hour, $mday,$mon,$year,$wday,$yday,$isdst) =
        localtime($unix_time);
    return sprintf("%04d%02d%02dT%02d%02d%02d",
                   1900+$year,$mon+1,$mday,$hour,$min,$sec);
}

1;

Net::Amazon::S3 for perl で、aws s3にあるファイル一覧を取得

https://docs.aws.amazon.com/cli/latest/reference/s3api/list-objects.html

aws s3の仕様は↑こちらで、 これを Net::Amazon::S3 for perl で実装したものが、以下。

大量のファイルでもあっても取得できるポイントは、 perl src内にあるmarkとnext_maker で、 これがページングの役割を担っています。

#!/usr/local/bin/perl
use strict;
use warnings;
use Encode;
use Net::Amazon::S3;
use Data::Dumper;

my $CONF =
    {aws_s3=>
     {client=>{
               host=>                   'ないしょ',
               aws_access_key_id =>     'ないしょ',
               aws_secret_access_key => 'ないしょ',
               retry => 1},
      bucket=>'ないしょ'},
     
     ls=>{max_keys=>5,     # 一度に取得するobj keyの制限
          delimiter=>"/",  # 不要と思いますが、これがないとs3がis_truncatedを返さない...
          max_exec=>10,    # lsを実行する回数制限
          sleep=>2}        # やさしく ls
    };


main( @ARGV );

sub main {
    my ($kyoten_code) = @_;

    unless( $kyoten_code ){
        return disp_usage();
    }

    my $s3 = Net::Amazon::S3->new(%{$CONF->{aws_s3}->{client}});
    $s3->ua( LWP::UserAgent->new(ssl_opts=>{verify_hostname =>0}) );

    my $bucket = $s3->bucket($CONF->{aws_s3}->{bucket} );

    my $list_opt =
        {prefix=>    $kyoten_code,
         max_keys => $CONF->{ls}->{max_keys},
         delimiter=> $CONF->{ls}->{delimiter} };
    my $pre_ls_res = {is_truncated=>1};

    my $ls_exec_count = 0;
    my $obj_keys_size = 0;
    
    while($pre_ls_res->{is_truncated} and
          $ls_exec_count++ < $CONF->{ls}->{max_exec} ){

        # 前回の続きから、ls する為、markerをset
        $list_opt->{marker} = $pre_ls_res->{next_marker};

        my $ls_res = $bucket->list($list_opt);
        
        $obj_keys_size += scalar(@{$ls_res->{keys}});
        
        for my $obj_info ( @{$ls_res->{keys}} ){
            # version idは別途、取得する必要あり
            my $obj_detail = $bucket->head_key($obj_info->{key});
            
            my $disp_str = join("\t",
                                $obj_info->{key},
                                $obj_detail->{'x-amz-version-id'} || '',
                                $obj_info->{size},
                                $obj_info->{etag},
                                $obj_info->{last_modified});
            print "$disp_str\n";
        }

        # 続きがある場合、is_truncated = true ですって
        $pre_ls_res->{is_truncated} = $ls_res->{is_truncated};
        $pre_ls_res->{next_marker} = $ls_res->{next_marker};

        sleep($CONF->{ls}->{sleep});
    }

    print "You have $obj_keys_size objects !!\n";
}

sub disp_usage {
    print "Usage: $0 HEIM_KYOTEN_CODE.\n";
}


1;

IT資産(ハードウェアやソフトウェア)を管理するDB table構成を考える - CMDB

↓こんな感じでOKかと思います

CREATE DATABASE it_assets CHARACTER SET utf8;
grant all privileges on it_assets.* to ないしょ@'%' identified by 'ないしょ';

CREATE TABLE data_center_doc (
id                      int AUTO_INCREMENT,
doc_name                varchar(100) not null unique comment 'DC文書名 or サービス名',
location                varchar(30)    comment '設置場所. 西日本, AWS',
data_center_doc_url     varchar(200)    comment 'DC文書の保管先url',
note                    varchar(300)    comment '概要',
dc_usage_price          int             comment 'DC利用料 円/月',
primary key(id)
)
COMMENT='データセンタA文書の単位で記載';

CREATE TABLE service (
dc_doc_name             varchar(100)   comment 'tbl:data_center_docのname',
id                      int AUTO_INCREMENT,
service_name            varchar(100) not null unique comment 'サービス名',
note                    varchar(300)    comment '概要',
monitor_sys_url         varchar(200)    comment '例:xymon画面のurl',
service_start_date      date            coment 'サービス開始日',
sla_price               int             comment '運用費 円/月',
sla_start_date          date            comment '運用契約 開始日',
sla_doc                 varchar(200)    comment '運用契約書の保管先url',
owner_dept              varchar(50)     comment 'オーナ部署',
owner_man               varchar(50)     comment 'オーナ部署 責任者、担当者',
our_sales_dept          varchar(50)     comment '自社 営業 部署',
our_sales_man           varchar(50)     comment '自社 営業 責任者、担当者',
our_dev_dept            varchar(50)     comment '自社 開発 部署',
our_dev_man             varchar(50)     comment '自社 開発 責任者、担当者',
our_ops_dept            varchar(50)     comment '自社 運用 部署',
our_ops_man             varchar(50)     comment '自社 運用 責任者、担当者',
primary key(id)
)
COMMENT='運用契約の単位で記載';

ALTER TABLE service ADD CONSTRAINT service_dc_doc_name
FOREIGN KEY (dc_doc_name) REFERENCES data_center_doc (doc_name)
ON DELETE SET NULL ON UPDATE CASCADE;


CREATE TABLE service_option (
dc_doc_name             varchar(100)   comment 'tbl:data_center_docのname',
id                      int AUTO_INCREMENT,
type                    varchar(10)     comment 'ssl, dns, other',
name                    varchar(100) not null comment '名称',
note                    varchar(300)    comment '概要',
running_price           int             comment 'ランニング費 円/月',
vender                  varchar(50)     comment 'ベンダー',
start_date              date            comment '開始日',
nend_date               date            comment '終了日',
primary key(id)
)
COMMENT='dnsやssl証明書 等. dnsやsslの有効期限はscriptで自動checkしたい';

ALTER TABLE service_option ADD CONSTRAINT service_option_dc_doc_name
FOREIGN KEY (dc_doc_name) REFERENCES data_center_doc (doc_name)
ON DELETE SET NULL ON UPDATE CASCADE;


CREATE TABLE hardware (
dc_doc_name             varchar(100)   comment 'tbl:data_center_docのname',
id                      int AUTO_INCREMENT,
hw_type                 varchar(10)  not null comment 'オンプレ 統合WIN 統合DB AWS ',
hw_name                 varchar(100) not null comment '名称',
maker                   varchar(50),
model                   varchar(80)     comment '型番',
quantity                int default 1   comment '数量. 冗長化の場合、2以上',
note                    varchar(300)    comment '概要',
support_vender          varchar(50)     comment '保守業者',
support_start_date      date            comment '保守 開始日',
support_end_date        date            comment '保守 終了日',
extra_support_end_date  date            comment '特別 終了日',
support_end_note        varchar(300)    comment 'EOLのアナウンス有無等',
primary key(id)
)
COMMENT='サーバやロードバランサ、外付けドライブ 等';

ALTER TABLE hardware ADD CONSTRAINT hardware_dc_doc_name
FOREIGN KEY (dc_doc_name) REFERENCES data_center_doc (doc_name)
ON DELETE SET NULL ON UPDATE CASCADE;

-- 外部参照keyを貼られる側には、indexが必要みたい
create index hardware_hw_name on hardware(hw_name);


CREATE TABLE software (
dc_doc_name             varchar(100) comment 'tbl:data_center_docのname',
hw_name                 varchar(100) comment 'tbl:hardwareのhw_name',
id                      int AUTO_INCREMENT,
sw_type                 varchar(10)  not null comment 'OS MW AP OTHER',
sw_name                 varchar(100) not null,
maker                   varchar(50),
model                   varchar(200)     comment '型番 ex. uname -a. perl -v',
quantity                int default 1   comment 'ライセンス数',
note                    varchar(300)    comment '概要',
support_vender          varchar(50)     comment '保守業者',
support_start_date      date            comment '保守 開始日',
support_end_date        date            comment '保守 終了日',
extra_support_end_date  date            comment '特別 終了日',
support_end_note        varchar(300)    comment 'EOLのアナウンス有無等',
primary key(id)
)
COMMENT='OSやミドルウェア 等. 各swのversionはxymon経由で自動収集したい';

ALTER TABLE software ADD CONSTRAINT software_dc_doc_name
FOREIGN KEY (dc_doc_name) REFERENCES data_center_doc (doc_name)
ON DELETE SET NULL ON UPDATE CASCADE;

ALTER TABLE software ADD CONSTRAINT software_hw_name
FOREIGN KEY (hw_name) REFERENCES hardware (hw_name)
ON DELETE SET NULL ON UPDATE CASCADE;

ALTER TABLE software ADD CONSTRAINT software_hw_name
FOREIGN KEY (hw_name) REFERENCES hardware (hw_name)
ON DELETE SET NULL ON UPDATE CASCADE;