OmniAICreator/Qiita-1.07M huggingface dataset:
タスク管理ツールをReact + ASP.Coreで作る 009詳細画面構築(表示領域の設計と整備)


[前回](https://qiita.com/mosuma/items/e0f535ab4406110b6334)の続きです

### ガイド
全体Index：[タスク管理ツールをReact + ASP.Coreで作る - Index](https://qiita.com/mosuma/items/88e40dc44e7bff1a54d8)


# 概要
次作っていくのは、個々のタスクの詳細を閲覧したり、編集したりする機能です。
想定では、以下みたいな感じです。

とにかくまず、絵で言うIssueのゾーンを作ってみようと思います。



![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2664470/63d6a991-c7f3-2e0a-ccde-cefe6e381f16.png)



構想は以下です

- Row/Colで構造を作る
- タスクを選択したらIssueが飛び出してくるという構造を目指すが、いきなりそこに行くのは大変なのでまずは固定にする
- 列の構造はタスクが選択されている/されていないで変えない(Due Dateは隠さない)




こんな風に領域分けしてみます

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2664470/5e9e8605-3219-e953-f180-3d14803cc0c9.png)

全部一気に構築するのは大変なので、今回もまずこの構造を先に作ってみようと思います


```diff_tsx:App.tsx
- import React from 'react';
- import logo from './logo.svg';
import './App.css';
- import { WeatherForecast } from './WeatherForecast';
- import { TaskList } from './TaskList';
+ import { TaskOperationMain } from './TaskOperationMain';

function App() {
  return (
    <div>
-        <TaskList />
;       <TaskOperationMain />
     </div>
  );
}

export default App;
```


```diff_tsx:TaskEdit.tsx
interface Props {
    id_task: string
}


export const TaskEdit = ({id_task}: Props) => {    
    

    return (
        <div>
            <h3>Task Detail</h3>
            <p>{id_task} : selected</p>
        </div>
    )
}
```


```diff_tsx:TaskOperationMain.tsx
import { Col, Row } from "react-bootstrap"
import { TaskEdit } from "./TaskEdit"
import { TaskList } from "./TaskList"


export const TaskOperationMain = () => {
    
    

    return (
        <div>
            <Row>
                <Col>
                    <TaskList />
                </Col>
                <Col>
                    <TaskEdit id_task="dammy" />
                </Col>
            </Row>
        </div>
    )
}
```



3ファイル作って呼び出します。
これで実行してみると、以下の様になります。



![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2664470/7ee62864-fa0f-d3bc-ce78-2477dfa992d6.png)

これで、後はリストにリンクを仕込んで、リンクを踏むとそれに従ってdetail部分を少しずつ作りこんでいけばよさそうです。





今回は以上です。
続きは[次回](https://qiita.com/mosuma/items/e05bb7ac8e8ee80fda42)です
Rubyのプログラムを実行するには?

Atcoderの問題を解くときに、任意の環境でコードを書く必要があったので、VScodeを開いてわざわざrails cでコンソールを開いて、1行ずつ実行していた苦い思い出があったので、共有しておきます。

rubyがインストールされた環境でターミナルを立ち上げ

rubyに何かプログラムを書いた後に

ruby 〇〇.rbと入力するとそのプログラムが実行されます。
Photon OS と ISC DHCP で DHCP サーバーを構築

以前の記事で、Photon OS に dnsmasq をインストールして、DNS サーバー兼 DHCP サーバーを構築しました。

https://qiita.com/iwaseyusuke/items/4dc5188e204cd7eb9ca1

今回は、DHCP サーバー機能に絞って、構築していく手順を紹介します。

なぜわざわざ？とも思いますが、dnsmasq で DNS サーバーも同時に構築しようとする場合、Photon OS 4.0 以降では、systemd-resolved とポート競合を起こしてしまったり、手順に若干の落とし穴があります。そのため、サクッと DHCP サーバーだけ構築したい場合には、ISC DHCP (isc-dhcp-server) を使った方が簡単なので、その紹介になります。

## 前提

Photon OS がデプロイ済みであり、最低限の設定として、こちら↓のあたりが行われている想定で、手順を記載しています。
ただし、全ての手順を実施する必要は無いので、詰まった時に参照いただければ良いです。

https://qiita.com/iwaseyusuke/items/ef56cd5b2c59ff757fa2

また、執筆時点で、Photon OS は ver 4.0 を利用しています。

```sh
# cat /etc/photon-release
VMware Photon OS 4.0
PHOTON_BUILD_NUMBER=2f5aad892
```

## パッケージのインストール

今回利用する ISC DHCP のサーバー機能のパッケージをインストールします。

```sh
tdnf install -y dhcp-server
```

ちなみに... パッケージ名は `dhcp-server` ですが、下記の `SPEC` ファイルを見てもらうと、中身が ISC DHCP を使っていることが分かります。

https://github.com/vmware/photon/blob/master/SPECS/dhcp/dhcp.spec

## 設定ファイルの修正

DHCP サーバーとしての設定を記載していきます。

```sh
vi /etc/dhcp/dhcpd.conf
```

設定例:

```conf:/etc/dhcp/dhcpd.conf
subnet 192.168.122.0 netmask 255.255.255.0 {
  range 192.168.122.100 192.168.122.200;
  option routers 192.168.122.254;
  option domain-name-servers 192.168.1.1;
  option ntp-servers 192.168.1.1;
}
```

もし、特定のホスト (MAC アドレス) に対して、固定 IP アドレスを振りたい場合には、下記のような設定を追記します。

```conf:/etc/dhcp/dhcpd.conf
host dhcp-tester {
  hardware ethernet xx:xx:xx:xx:xx:xx;
  fixed-address 192.168.122.101;
}
```

また、DHCP サーバーにする VM が、複数の NIC を持っている場合、どの NIC を使うのか指定する必要があります。
その場合は、下記のように設定します。

```sh
vi /etc/default/dhcpd
```

設定例:

```conf:/etc/default/dhcpd
DHCPD_OPTS=
INTERFACES="eth1"
```

## サービスの起動

まずは、サービスを有効化します。

```sh
systemctl enable dhcp.service
```

次に、サービスを起動します。

```sh
systemctl start dhcp.service
```

サービスの起動を確認して、正しく起動しているか見ておきましょう。

```sh
systemctl status dhcp.service
```
CSSだけで流体シェイプを作ってみた

# はじめに

流体シェイプ（Fluid Shape）とは、スライムのように液体が波打つ動きをする曲線の円形のことです。

実装する時は、プラグインを入れたり[Blob Animation](https://blobanimation.com/)のようなアニメーションを細かく設定できる調整ツールで、HTMLにコードを貼り付けて表示するのが一般的かとは思いますが、実はCSSだけでも簡単に実装することができます。

# 実装編

このような流体シェイプを作っていきたいと思います。

<p class="codepen" data-height="300" data-default-tab="html,result" data-slug-hash="vYrOwrX" data-user="ryota-shimajiri" style="height: 300px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;">
  <span>See the Pen <a href="https://codepen.io/ryota-shimajiri/pen/vYrOwrX">
  Untitled</a> by 島尻 亮汰 (<a href="https://codepen.io/ryota-shimajiri">@ryota-shimajiri</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async src="https://cpwebassets.codepen.io/assets/embed/ei.js"></script>

## 解説編

まずは以下のようなHTMLを用意します。
このfluidに流体シェイプのCSSを当てていきます。

```HTML:index.html
  <div class="container">
    <div class="fluid"></div>
  </div>
```

一応、containerで見やすいように中央寄せしてます。これは別になくても良いです。
以下、CSSです。

```css:style.css
.container {
    margin-top: 5%;
    display: flex;
    justify-content: center;
    align-items: center;
}

.fluid {
    background: aqua;
    width: 50vh;
    height: 50vh;
    animation: fluid-animation 10s ease 0s infinite normal;
}

@keyframes fluid-animation {
    0%, 100% {
        border-radius: 59% 41% 21% 79% / 62% 53% 47% 38%;
    }
    50% {
        border-radius: 26% 74% 52% 48% / 52% 32% 68% 48%;
    }
}
```

[animation](https://developer.mozilla.org/ja/docs/Web/CSS/animation)プロパティと[@keyframes](https://developer.mozilla.org/ja/docs/Web/CSS/@keyframes)を使うことにより、CSSだけで簡単にアニメーションの設定を行うことができます。

animationの使い方は、
```
animation: {変数名} {変化時間} {進行具合 何秒目から開始するか} {ループ} {交互に反対再生するかどうか}
```
このようになります。

@keyframesは、アニメーションの開始から終了までの間にどのようなアニメーションを行うのかを指定することができます。
0%はアニメーション開始時で、100%はアニメーション終了時となります。
また0％〜100％の間で段階を指定することもできます。

肝心のアニメーションの中身ですが、やっていることは非常に単純で、
border-radiusで要素の境界の外側の角を丸めているだけです。

border-radiusの指定は、以下のサイトのようなジェネレーターを使用して、
実際に値を確認しながら調整すると良いかと思います。

Border Radiusジェネレーター
https://web-toolbox.dev/tools/border-radius-generator

## 応用編

@keyframesのfluid-animationにさらに色々と足してみます。
段階を細かく指定したり、色やサイズを変更することでより複雑な流体シェイプが出来上がりました。

これはあくまで一例ですが、このように色々と試しても面白いですね。

<p class="codepen" data-height="300" data-default-tab="html,result" data-slug-hash="KKedKpb" data-user="ryota-shimajiri" style="height: 300px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;">
  <span>See the Pen <a href="https://codepen.io/ryota-shimajiri/pen/KKedKpb">
  Untitled</a> by 島尻 亮汰 (<a href="https://codepen.io/ryota-shimajiri">@ryota-shimajiri</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async src="https://cpwebassets.codepen.io/assets/embed/ei.js"></script>

## 参考

https://developer.mozilla.org/ja/docs/Web/CSS/animation

https://developer.mozilla.org/ja/docs/Web/CSS/@keyframes
Flutterでshared_preferencesをいい感じに使う

Flutterでアプリ開発をする際にかなりの確率で使うことになるshared_preferencesをいい感じに使う方法を見つけたので備忘録として書いておく

こちらが公式ページ

https://pub.dev/packages/shared_preferences

インストール用のコマンド
```
flutter pub add shared_preferences

```


keyの管理をenumを使って行う
```
enum Pref {
  sample,
}
```

shared_preferencesを利用するためにインスタンスを宣言する
何度も取得する必要はないので起動時にでもinitializeSharedPreferencesを読んであげればいいかと思います
```
late SharedPreferences _prefs;

Future<void> initializeSharedPreferences() async {
  _prefs = await SharedPreferences.getInstance();
}
```


shared_preferencesには検索、削除、設定、取得の４パターンがあるので、これをextensionを利用して関数化する
1つ問題なのがshared_preferencesは型ごとに関数が分けられている
そのせいで共通化できないのでGenericsを利用することで型ごとに振り分ける
```
extension PrefExtension on Pref {
  bool get contains => _prefs.containsKey(name);

  Future<bool> get remove => _prefs.remove(name);

  Future<bool> set<T>(T value) {
    if (T == String) {
      return _prefs.setString(name, value as String);
    } else if (T == int) {
      return _prefs.setInt(name, value as int);
    } else if (T == double) {
      return _prefs.setDouble(name, value as double);
    } else if (T == bool) {
      return _prefs.setBool(name, value as bool);
    }

    return Future.value(false);
  }

  T? get<T>() {
    if (T == String) {
      return _prefs.getString(name) as T?;
    } else if (T == int) {
      return _prefs.getInt(name) as T?;
    } else if (T == double) {
      return _prefs.getDouble(name) as T?;
    } else if (T == bool) {
      return _prefs.getBool(name) as T?;
    }

    return null;
  }
}
```

利用する場合は下記のように呼んであげます
またgetの時にそれぞれ型を宣言する必要があるのでそれだけ忘れずに行ってください
```
// 検索
Pref.sample.contains
// 削除
await Pref.sample.remove
// 設定
await Pref.sample.set('テスト')
// 取得
Pref.sample.get<String>()
```

テスト内容はこんな感じです
```
  test('string preferences test', () async {
    SharedPreferences.setMockInitialValues({});
    await initializeSharedPreferences();

    // 空チェック
    expect(Pref.sample.contains, false);
    expect(Pref.sample.get<String>(), null);

    // 値セット
    expect(await Pref.sample.set('テスト'), true);
    expect(Pref.sample.contains, true);
    expect(Pref.sample.get<String>(), 'テスト');

    // 値削除
    expect(await Pref.sample.remove, true);

    // 空チェック
    expect(Pref.sample.contains, false);
    expect(Pref.sample.get<String>(), null);
  });
```

現在のshared_preferencesではStringのListしか対応していないので下記のようになります
もしintのListを保存したいのであればStingに変換して保存してあげればいいかと思います
独自クラスの場合はMapにしてからString側のjsonにする感じですね
```
  Future<bool> setList<T>(List<T> value) {
    if (T == String) {
      return _prefs.setStringList(name, value as List<String>);
    }

    return Future.value(false);
  }

  List<T>? getList<T>() {
    if (T == String) {
      return _prefs.getStringList(name) as List<T>?;
    }

    return null;
  }
```

# 類似
https://qiita.com/slovenly_engineer/items/224da5a03e618035b9d9
Denoでdelay()を使って待機処理

Denoで待機処理するのん、どうするんかなと思って試してみた。
こんな感じ。
すごくシンプル。

```ts
import { delay } from "https://deno.land/std@0.161.0/async/mod.ts";

console.log(new Date());
await delay(1000);
console.log(new Date());
```

[delay | /async/mod.ts | std@0.161.0 | Deno](https://deno.land/std@0.161.0/async/mod.ts?s=delay)
[Deno 標準ライブラリ async | Octo's blog](https://www.ccbaxy.xyz/blog/2022/01/29/js37/)


ちなみに、Nodeではこんな感じ。
```ts
import { setTimeout } from 'node:timers/promises'
await setTimeout(1000);
```
Node v.15から、setTimeout()はPromise返してくれるようになったらしい。
[Timers | Node.js v19.0.0 Documentation](https://nodejs.org/api/timers.html#timerspromisessettimeoutdelay-value-options)
[setTimeoutをawait/asyncで書く方法](https://zenn.dev/yogarasu/articles/33e666cac2a646)

ちなみに、ちなみに従来的なやり方はこんな感じ。
```ts
await new Promise((resolve) => setTimeout(resolve, 1000));
```

[awaitできるsetTimeoutを1行で書く方法 - Qiita](https://qiita.com/suin/items/99aa8641d06b5f819656)
Pythonのメンバ変数を別のファイルから変更する。

### やったこと

非常に初歩的なことですが、Pythonのクラスのメンバ変数を外から変更する方法について備忘録としてまとめました。

### コード

まず、`test1.py`にクラスの情報を記述します。

~~~python: test1.py
class TestClass01:
    #メンバ変数
    value1 = 50
~~~

そしてそのメンバ変数である`value1`を`test2.py`から変更してみます。

~~~python: test2.py
import test1

class1 = test1.TestClass01()
print(class1.value1)

test1.TestClass01.value1 = 40
class2 = test1.TestClass01()
print(class2.value1)
print(class1.value1)
~~~

`test1.TestClass01.value1` というような形で直にメンバ変数にアクセスしています。
### 実行結果
~~~
50
40
40
~~~

このようになりました。

一番初めに`TestClass01`から作ったインスタンスである`class1`の`value1`は当然ながら初めに定義された通り`50`となっています。
`test1.TestClass01.value1 = 40`にてメンバ変数を変更した後に作成したインスタンスである`class2`の`value1`は`40`になっていますね。これでクラスのメンバ変数を直接変更できることが確認できました。
その後、再度`class1.value1`を呼び出していますが、ここで`value1`が`40`になっていますね。メンバ変数の変更前に作成したインスタンスもその影響を受けるということがわかりましたね。
Python3で静的Webページをスクレイピング

# 開発環境、前提

- macOS Ventura 13.0
- Rancher Desktop 1.6.1
前提：dockerコマンド、docker-composeコマンドを使える
- ターミナル、またはVisual Studio Code

# やること

- 入力：Webページのリンクを記載したテキスト（※）
- 出力：パースした情報を記載したテキスト

処理
1. 各行のWebページのhtmlをダウンロードする
1. HTMLをダウンロードする
1. いい感じにパースして必要な情報をパースする

※ 注意
- この記事では、実験的にYahoo!ファイナンスの日経平均株価とダウ平均を取得してみる。
- 一般的に、Webページを定期的に／網羅的に／大量にスクレイピングする場合、国内法や利用規約に準拠しているか、確認する必要がある。
- __筆者はこの記事で、Yahoo!ファイナンスを定期的に／網羅的に／大量にスクレイピングして良いかどうか確認していない。推奨もしていない。__

# ディレクトリ構造

```
scraping-py3 % tree 
.
├── Dockerfile
├── docker-compose.yml
├── out
│   └── （実行すると　scrape-result.txt　が増える。）
├── scraping-py3.code-workspace（Visual Studio Code の設定ファイル。直接の関係はない。）
└── src
    ├── links.txt
    └── scrape.py3
```

# 各ファイル


```dockerfile:Dockerfile
FROM python:3.11-buster

RUN apt-get update
RUN pip install requests beautifulsoup4

RUN useradd app --create-home	
USER app
WORKDIR /home/app
```

Composeファイル Version 3
```yaml:docker-compose.yml
services:
  scrape:
    build: 
      context: "."
      dockerfile: "./Dockerfile"
    container_name: "scrape-c"
    tty: true # keep container running
    volumes:
      - type: "bind"
        source: "./src"
        target: "/home/app/src"
      - type: "bind"
        source: "./out"
        target: "/home/app/out"
```

スクレイピングする対象のWebページ
```text:links.txt
https://finance.yahoo.co.jp/quote/998407.O
https://finance.yahoo.co.jp/quote/^DJI
```

```py:scrape.py3
import requests
from bs4 import BeautifulSoup

def main():
    print('Hello, scrape!')
    scrape()
    print('Bye, scrape!')

def scrape():
    workdir = "/home/app"

    # read links.txt
    with open(workdir + "/src/links.txt") as f:
        links = f.readlines()

    # write scrape-result.txt
    file = open(workdir + "/out/scrape-result.txt", "w")
    file.close()
    file = open(workdir + "/out/scrape-result.txt", "a")
    for link in links:
        parsed = parse(link)
        file.write(parsed)
        file.write('\n')
    file.close()

def parse(link: str) -> str:
    response = requests.get(link, [])
    soup = BeautifulSoup(response.text, "html.parser")

    elems = soup.find_all("span")
    getStr = lambda elem: elem.getText()
    spanTexts = map(getStr, elems)
    addToPf = list(spanTexts).index("ポートフォリオに追加")
    parse = getStr(elems[addToPf - 2]) + " " + getStr(elems[addToPf - 1])

    # Webページの調査用    
    # parse = "\n".join(spanTexts)

    return parse

if __name__ == '__main__':
    main()
```

# 実行

```zsh:zsh ターミナル
docker-compose up --build
```
control+C を押下するまでコンテナが起動し続ける。

　別のターミナルを開いて、起動中のコンテナの中でbashを起動
```zsh:zsh ターミナル
docker exec -it scrape-c /bin/bash     
```

```bash:コンテナのbash
python3 /home/app/src/scrape.py3
```

`/home/app/out/scrape-result.txt` が作成される。
【AWS】作成から一定期間経過したSnapshotを自動削除する仕組みを導入してみた

# 前提
メンテナンス時など、バックアップを取る際にスナップショットを手動で取得している。

# 課題
メンテナンスが問題なく完了した後、不必要になったスナップショットの削除を行う運用ルールがなく、無駄なコストが発生していた。

# 目的
スナップショット作成から一定期間経過したものは削除する仕組みをLambdaに関数登録し、トリガーとしてCloudWatch Eventsで日時実行する。
原則AMIに登録されているスナップショットの削除は行わない。

# ソース
~~~python:auto_delete_snapshot.py
import boto3
from datetime import datetime, timedelta
import dateutil.tz

########## 変数定義 ##########
# AWSアカウントID
acountId = "xxxxxxxxxx"
# 何日前以前を削除対象とするか指定 default=7
days = 7

########## 削除処理 ##########
def lambda_handler(event, context):
    # 削除対象日付を取得
    delete_date = (datetime.now(dateutil.tz.tzlocal()) - timedelta(days=days))
    print(delete_date)

    # 本AWSアカウント上に存在するスナップショットを全て取得
    ec2 = boto3.client("ec2")
    snapshots = ec2.describe_snapshots(
        Filters=[
            {
                "Name": "owner-id",
                "Values": [acountId]
            }
        ]
    )["Snapshots"]
    # 本AWSアカウント上に存在するAMIを全て取得
    images = ec2.describe_images(
        Filters=[
            {
                "Name": "owner-id",
                "Values": [acountId]
            }
        ]
    )["Images"]
    print('全スナップショット')
    print(snapshots)
    print('全AMI')
    print(images)

    # 作成から7日以上経過したスナップショットを取得
    old_snapshots = [s for s in snapshots if s["StartTime"] < delete_date]

    # AMIに登録されているスナップショットを削除対処から除外する
    for x in images:
        for y in old_snapshots:
            for block_devices in x['BlockDeviceMappings']:
                if 'Ebs' in block_devices:
                    if block_devices['Ebs']['SnapshotId'] == y['SnapshotId']:
                        old_snapshots.remove(y)

    print('AMI以外の削除対象スナップショット')
    print(old_snapshots)

    # 削除対象のスナップショットを削除
    for s in old_snapshots:
            ec2.delete_snapshot(SnapshotId = s['SnapshotId'])
            print(s['SnapshotId'] + "を削除しました")
~~~

# まとめ
実行結果は以下から確認可能です。
`Lambda > モニタリング > CloudWatchのログを表示 > ログストリームから該当のログを選択`

AMIに登録されているスナップショットの扱いをどうするのか、現場の運用ルールによってはAMIの登録解除→スナップショットの削除までやってもいいと思います。

今回はLambda、CLIからのSnapshot、AMIの扱い、CloudWatch Event、IAMのポリシーとロール、アクセス許可設定について理解が深まりました。

最後まで読んでいただきありがとうございました！
Open xINT CTF 2022 Writeup

# はじめに

こんばんは。2022年10月29日にAVTokyo主催のOpen xINT CTF 2022が開催されました。
暇だったので研究室のメンバーを誘って二人で参加してみました。
結果としては800点を獲得することができ、44位という形でした。まぁ、初参加にしては頑張った方かな...
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1247693/e2b4af14-66db-1aff-3d41-5b8a01690a76.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1247693/6530bc09-5428-f0e6-9691-318076eef2f8.png)

# Writeup
解けた問題に関して、自分なりの解き方を載せていこうと思います。

## NET

### whois

> 194.146.200.33 のAS番号を答えよ.
Answer the AS number for 194.146.200.33.


https://qiita.com/snumano/items/f776cb8c5f539d76b927

こちらの記事をそのままIPアドレスだけ変えればできました。

```
$ curl ipinfo.io/194.146.200.33/org
```
Flag = <strong>AS42574</strong>

## PLACE

### spacious

> 写真に写っている場所の名前を答えよ。
What is the name of this place in the photo?

こちら2つの写真が渡されました。

![park1.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1247693/f0e4a861-5cea-705e-2725-d279d36ccb9d.jpeg)

![park2.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1247693/8dd4a898-8b86-6f93-ec82-c14d05a5540b.jpeg)
注目したのは1枚目の写真上部分の赤いTの看板(?)。
`モビリティパ...  TOYOTA`と読めて、パ以降が葉っぱで隠れてて見えない。
![park1_zoom.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1247693/55c66bc6-e548-0ee0-eb8a-60a2b21fa911.jpeg)
調べてみたところ`トヨタモビリティパーツ`という自動車関連部品・用品の会社みたいでした。
とりあえず`モビリティパーツ toyota`で検索し、問題の画像に近いものを探してみると...
周りの雰囲気とかも合ってそう。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1247693/9a8f8737-8918-4819-af24-7c225708b587.png)
画像の詳細を見てみると`東京・山梨統括支社`らしいです。

Google Mapで確かめてみましょう。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1247693/d1b9b462-b24a-c694-c2ad-956a84190af9.png)
問題で渡された1枚目と撮影された方角を合わせてみたところ見事に一致。

あとはどこから撮影されたのかを考える。

2枚目の画像からどうやら地面が灰色の公園みたいに見える。

Google Mapで周りの公園を探してみると....

Flag = <strong>いきいき広場</strong>


### VIDEO
> 動画の開始地点の座標を答えよ
フラグ形式: Nxx.xxxx Exxx.xxxx
Answer the coordinates of the start of the video.
FlagFormat: Nxx.xxxx Exxx.xxxx

リンクが渡され飛んでみると動画が共有されていた。
内容は20秒のドライブレコーダー。
動画の最初はこんな感じ。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1247693/7e8b99b7-7c64-894b-a77b-9fd48a5b9326.png)
とりあえず動画を流してみると、動画の最後らへんに場所が分かりそうな情報があった。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1247693/067d2c9b-ea95-88d1-eb0c-27ca3e213185.png)
だいぶ画質が悪いが、かろうじて`君津PA`に見える。

Google Mapで調べたら、ストリートビューで動画の開始地点を探す。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1247693/140fdcbe-37c3-5cc2-bbaa-51b3eabc534a.png)
あった。右クリックで座標をコピー。

Flag = <strong>N35.2849 E139.9251</strong>

# 感想
OSINT系CTFは初めての参加でした。
思ったより難易度が高くてだいぶ試行錯誤しながら問題を解いていました。
あと少しで解けそう...!みたいな問題がいくつかあったけど、時間が足らず...
プログラミングの知識とかツールの知識とかがほぼいらず、ググる能力があればCTF初心者でも入りやすいなと感じました。
また機会があったらリベンジしたいです。
TypeScript でMapからObject、ObjectからMapへの変換

# はじめに
TypeScript でMapからObject、ObjectからMapへの変換する方法がいまいちわかってなかったので、色々試してみた。たぶん、JavaScriptでも一緒。

# 変換方法
## Map -> Object
`Object.fromEntries()`で変換できる。引数にMapを直接渡せる。

[Object.fromEntries() - JavaScript | MDN](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Object/fromEntries)

## Object -> Map
`new Map(Object.entries())`で変換できる。引数にObjectを渡す。

[Object.entries() - JavaScript | MDN](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Object/entries)
[Map - JavaScript | MDN](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Map)

# コード
こんな感じ。

```typescript
type THoge = {
  fuga: string;
  piyo: number;
};

const map = new Map<string, THoge>();
map.set("1", { fuga: "fuga", piyo: 1 });
map.set("2", { fuga: "fuga", piyo: 2 });
console.log(map);
// Map { "1" => { fuga: "fuga", piyo: 1 }, "2" => { fuga: "fuga", piyo: 2 } }
console.log(JSON.stringify(map));
// {}
console.log(JSON.stringify(map.entries()));
// {}

const object = Object.fromEntries(map);
console.log(JSON.stringify(object));
// {"1":{"fuga":"fuga","piyo":1},"2":{"fuga":"fuga","piyo":2}}
const object2 = Object.fromEntries(map.entries());
console.log(JSON.stringify(object2));
// {"1":{"fuga":"fuga","piyo":1},"2":{"fuga":"fuga","piyo":2}}
console.log(JSON.stringify([...map]));
// [["1",{"fuga":"fuga","piyo":1}],["2",{"fuga":"fuga","piyo":2}]]
console.log(JSON.stringify([...map.entries()]));
// [["1",{"fuga":"fuga","piyo":1}],["2",{"fuga":"fuga","piyo":2}]]

const keys = map.keys();
const values = map.values();
const mapEntries = map.entries();
console.log([...keys], [...values], [...mapEntries]);
// [ "1", "2" ] [ { fuga: "fuga", piyo: 1 }, { fuga: "fuga", piyo: 2 } ] [ [ "1", { fuga: "fuga", piyo: 1 } ], [ "2", { fuga: "fuga", piyo: 2 } ] ]

const entries = Object.entries(object);
console.log(entries);
// [ [ "1", { fuga: "fuga", piyo: 1 } ], [ "2", { fuga: "fuga", piyo: 2 } ] ]
const map2 = new Map(entries);
console.log(map2);
// Map { "1" => { fuga: "fuga", piyo: 1 }, "2" => { fuga: "fuga", piyo: 2 } }
```

# ポイント
- `Object.fromEntries()`は引数に`Iterable`なものを期待していて、`map`でも`map.entries()`も同じ結果になる
- Mapは`JSON.stringify()`できない
- `[...map]`も`[...map.entries()]`も同じ結果になる。

# おわりに
こういうの苦手なので、動かしながら整理できてよかった。

# 参考
ここらわかると、試さなくても関数定義見れば動作想像できそう。
[反復処理プロトコル - JavaScript | MDN](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Iteration_protocols)

これ、分かりやすかった。
[JavaScript の イテレータ を極める！ - Qiita](https://qiita.com/kura07/items/cf168a7ea20e8c2554c6#12-%E3%82%A4%E3%83%86%E3%83%A9%E3%83%96%E3%83%AB-iterable-%E3%81%A8%E3%81%AF)
AWS CDK初心者が爆速でLambda Layerにpythonライブラリを入れた裏技

# はじめに
AWS CDKについて初心者用にまとめた記事はこちら

https://qiita.com/besmero628/items/dbab08287a8bc2d1893a


# 概要
最近AWSCDKを使用してpythonベースのlambdaを書いたのですが、pandasやらboto3やら使用する必要がありました。

もちろん素のpythonには入っていないライブラリなのでlambdaにパッケージごと入れる必要があるのですが、いちいちzipで固めて入れるのはめんどくさい。。。

AWSCDKをpythonで書き進めている記事もネットには少なくどうしたものかと思っていたのですが、色々調べていくうちに先人の知恵を借りられそうだったのでメモしておきます。


# 手順
:::note info
cdk languageはpythonを選択しています。
:::
## ① lambdaの作成
手始めにlambdaを作成します。
```python:stack.py
from aws_cdk import(
 aws_lambda as _lambda
 Stack
)

class HogeStack(Stack):

    (省略)

    my_func = _lambda.Function(
        self, "MyFunction",
        function_name = "my_function",
        runtime = _lambda.Runtime.PYTHON_3_8,
        code = _lambda.Code.from_asset('lambda/test_asset')
        handler = 'test_asset.handler'
    )
```

## ② 先人のArn情報を確認する
今回はlambdaにpandasを入れてみたいと思います。
お世話になる先人の情報はここ。

https://github.com/keithrozario/Klayers/tree/master/deployments/python3.8

こちらでlayerに使用できるArn情報を公開してくれています。
lambdaでバックグラウンド的にライブラリを必要とする場合にlambdalayerと呼ばれるものを使用します。
先に述べた通り、ライブラリをzipで固めてlayerにすることは可能ですが、今回は既に公開されている先人のものをお借りしようと言うわけです。

上記のページにアクセスすると以下表示となります。

![スクリーンショット 2022-10-29 21.58.14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1005334/18fb023f-16f1-bec0-65c4-d896f070ba44.png)

今回は無難にTokyo regionで作成しようと思いますので、該当のjsonをクリック。
jsonの中から「pandas」の情報を探します。

![スクリーンショット 2022-10-29 21.59.37.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1005334/49a25274-28b1-e7f8-25a7-563ab79e98e4.png)

発見しました。
後で使用するので、Arn情報をコピーしておきます。

## ③ layerを追加する
①で作成したlambdaに②で確認したlayerを追加します。

```python:stack.py
class HogeStack(Stack):

    (省略)

    my_func.add_layers(
        _lambda.LayerVersion.from_layer_version_arn(
            self, 'pandas',
            'arn:aws:lambda:ap-northeast-1:770693421928:layer:Klayers-p38-pandas:8'
        )
    )
```

layerの追加は、定義したlambdaにadd_layerメソッドで行うことができます。
またこの時、layerとしてarn情報を与えたいときは、from_layer_version_arnでオブジェクト化したものを与えてあげることが必要です。

ここで重要なのは、my_funcに直接from_layer_version_arnを指定できないことですかね。
一度Arnをオブジェクト化（？）してあげる必要があるようです。

公式は以下参照。

https://docs.aws.amazon.com/cdk/api/v2/python/aws_cdk.aws_lambda/LayerVersion.html#aws_cdk.aws_lambda.LayerVersion.from_layer_version_arn


## ④ deployする
最後にdeployしてあげれば、無事lambdaにlayerをつけられます。

```shell:
$ cdk deploy
```


# まとめ
自分で一から環境を作成するより断然早い！（はず！）
あくまで自己責任だとは思いますが、とりあえず動作を確認したい時などサクッとできるので便利です。
Tanzu CLI コマンドを使って kubeconfig ファイルをエクスポートするシンプルな方法

`tanzu` CLI コマンドを使って、特定の Tanzu Kubernetes クラスタにアクセスするための kubeconfig ファイルを取得するコマンドです。

## TL;DR

下記のコマンドを実行することで、カレントディレクトリに kubeconfig ファイルをエクスポートすることができます。

フォーマット:

```sh
tanzu cluster kubeconfig get CLUSTER_NAME --export-file ./kubeconfig [--admin]
```

実行例:

```sh
$ tanzu cluster kubeconfig get tkc01 --export-file ./kubeconfig --admin
Credentials of cluster 'tkc01' have been saved
You can now access the cluster by running 'kubectl config use-context tkc01-admin@tkc01' under path './kubeconfig'
```

## 解説

### 前提

利用しているは、Tanzu Kubernetes Grid 1.6 であり、下記のドキュメントに従って、`tanzu` CLI コマンドがインストールされていること。

https://docs.vmware.com/en/VMware-Tanzu-Kubernetes-Grid/1.6/vmware-tanzu-kubernetes-grid-16/GUID-install-cli.html

そして、kubeconfig をエクスポート (取得) したい Tanzu Kubernetes クラスタがデプロイ済みであること。

```sh
$ tanzu cluster list
  NAME           NAMESPACE  STATUS   CONTROLPLANE  WORKERS  KUBERNETES        ROLES   PLAN  TKR
  tkc01          default    running  1/1           3/3      v1.23.8+vmware.2  <none>  dev   v1.23.8---vmware.2-tkg.1
```

### 指定オプション

特にオプションを指定せずに、下記のようにコマンドを実行すると、kubecofing のデフォルトの配置先 `$HOME/.kube/config` に対して、kubeconfig の情報が "追記" され、また Tanzu Kubernetes クラスタに対して、非 Admin 権限でのアクセス権しか取得できません。

```sh
# 情報の格納先は、デフォルトの $HOME/.kube/config
tanzu cluster kubeconfig get tkc01
```

そのため、もし、Tanzu Kubernetes クラスタ (Workload クラスタ) のユーザーのために、kubeconfig を配布したい場合、`$HOME/.kube/config` には、Management クラスタの kubeconfig の Admin 権限の内容も含まれてしまうため、そのまま渡すのには適しません。

そこで、`--export-file` オプションを指定して、出力する先を指定しています。

`--admin` については、名前の通り、Admin 権限のある kubeconfig にするか否かのオプションですので、渡したいシーンに合わせて指定します。

### ファイルの使い道

エクスポートしたファイルは、Tanzu Kubernetes クラスタのユーザーに渡し、そのユーザーの `$HOME/.kube/config` として格納して貰えれば、ユーザーは `kubectl` コマンドを使って、Tanzu Kubernetes クラスタにアクセスすることができます。

ちなみに... Tanzu Mission Control (TMC) で Tanzu Kubernetes クラスタを管理している場合、TMC の UI から kubeconfig はダウンロードできるので、セルフサービス化もできて、とても便利ですよ！ (ちゃっかり宣伝)

## 参考

Tanzu Kubernetes Grid の公式ドキュメントにも、解説がありますので、より詳細な情報については、こちらをご参照ください。

https://docs.vmware.com/en/VMware-Tanzu-Kubernetes-Grid/1.6/vmware-tanzu-kubernetes-grid-16/GUID-cluster-lifecycle-connect.html#retrieve-workload-cluster-kubeconfig-2
【初心者】【Lambda】ログについて確認し原因をしっかり追究してみた

# はじめに
どうもAWS初心者です。
今回Lambdaについて学習を行いました。
S3を利用して画像のリサイズを行う事を目標に関数を作成しました。
S3バケットを2つ用意し1つ目のバケットに画像をアップロードした際に、2つ目のバケットにリサイズした画像を上げるといった動きです。
作成しテストまでは上手くいったのですが、実際に適当な画像を上げた際に動作しませんでした。
その事について記事にしてみようかと思います。

# 原因の予測
テストで使用したファイルと同じ拡張子のファイルを利用しました。
2つのファイルの違いはサイズであり、そこに原因があると思い、サイズの小さいファイルをアップロードしてみたら成功しました。
なのでファイルが大きいことが原因だと考えました。

# 原因の解明
クラウドウォッチのログを確認することで原因の解明をしようと思いました。
しかしログを見てもエラーはありませんでした。
エラーとしては出ていなかったのですが1つ1つよく見ていく中で原因が分かりました。
![スクリーンショット 2022-10-29 21.36.01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2920144/019b0691-1d1a-83da-4257-f7156b6f9e06.png)
こちらのログを見ると「Duration: 3042.84 ms	Billed Duration: 3000 ms」と書いてあります。
簡単に言うとリサイズを行うのに3042.84msかかったといった内容なのですが、ここを見て原因が分かりました。
タイムアウトの設定を３秒（3000ms）にしていたのですが、リサイズするのに３秒以上かかっていることが今回リサイズが行われなかった理由でした。なのでタイムアウトしたのでエラーとして残らなかった様です。（タイムアウトはエラーでは無いため）

ちなみに画像サイズは「Memory Size: 128 MB	Max Memory Used: 86 MB」を見ると分かるのですが128MBを超えるとダメなのですが86MBしか使用してないので原因ではない。

# 修正
関数の基本設定を編集してあげます。
タイムアウトの項目で3秒になっていたのでそこを10秒に伸ばします。
結果、失敗していた画像でもリサイズすることが出来ました！！

# おわりに
とても初歩的な内容だと思いますが、自身でログについて確認し原因をしっかり追究してみたので記事にしてみました。
ここまで読んでくださった方感謝です！
IBM Cloud: VSI for VPC環境でログインできなくなった環境をchroot jail環境から修正する方法

IBM Cloudには、デフォルトではrootユーザーに公開鍵暗号方式でSSHログインできるように初期構成されているが、一般的には構築・運用の途中で、

- /etc/ssh/sshd_configの設定間違い
- SELinuxの設定間違い
- OS Firewallの設定間違い
- PAMの設定間違い

などによりSSHでログインできなくなってしまったとか、rootユーザーにログインできなくなったケースなどの際に回復できる手順があることが望ましい。今回は、rootパスワードがわからなくなって環境を操作できなかったという想定で、Red Hat Enterprise Linux 8.4のboot時にカーネルオプションをVNCから編集することにより、rootパスワードをリセットする方法を紹介する。基本は[この方法](https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/8/html/configuring_basic_system_settings/changing-and-resetting-the-root-password-from-the-command-line_configuring-basic-system-settings#doc-wrapper)を踏襲すれば良い。


# 1. VNCコンソールを起動する。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/62127/f25673e0-b09b-8989-d4bf-4d3778da6df0.png)

# 2. VNCコンソールを起動したままOSを再起動する。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/62127/d45e236e-64a1-f0bc-3159-196d75fe8e3b.png)

# 3. GRUBの編集画面に入る
VNCコンソールを起動したまま、OSを再起動する。すると、以下のようなGRUBの画面が現れる。GRUBはデフォルトでは1秒程度で抜けてしまうので、リブートしたらすぐに下矢印キーを連打しておくと良い。`e`を押下して編集画面に入る。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/62127/af229f40-84d7-ef08-2333-5dc3ebcd991d.png)

# 4. カーネルコマンドの編集
`linux`と書かれている行に移動する。`Ctrl + e`で行末に移動できる。
- 以下のように`rd.break`を追加して`Ctrl + x`を押下すれば良いはずだが、これだと`Ending clean mount`の箇所で止まってしまった。![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/62127/cbef6ac3-65cb-87f4-504e-d3e9cc931485.png)![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/62127/342d5e23-f7d9-69b7-0af7-3d1c7acecb91.png)
- 回避策として、以下のように文末に`rd.break`を追加するだけでなく、`console=tty0 console=ttyS0,115200n8`という箇所も削除して、`Ctrl + x`を押下する。[参考記事](https://www.reddit.com/r/redhat/comments/fhw2gq/rdbreak_hangs_on_ending_clean_mount_rhel_7/)![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/62127/196f830d-0732-b351-d911-f6d0580d17e4.png)![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/62127/c372b067-b0db-2bbc-15d1-7dd7c4659bc0.png)

# 5. 以下のコマンドを実行してパスワードをリセットする

```shell:/sysrootはread onlyでマウントされているので、読み書きができるように再マウント
switch_root:/# mount -o remount,rw /sysroot
```
```shell:/sysrootにchroot
switch_root:/#  chroot /sysroot
```
```shell:変更・復元作業（rootパスワードの変更など）
sh-4.4# passwd
sh-4.4# touch /.autorelabel
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/62127/e3e48b21-18c4-c928-bdf1-8ad3dfcb5e10.png)


あとは、OSの再起動をすれば良い。
カセットテープをMP3化する方法

# はじめに
断捨離のためにカセットテープをMP3化して保管することにしました。

# 用意するもの
- カセットテープ
- イヤホンジャックがついているカセットコンポ
- マイクジャックがついているパソコン
    - Windows11 搭載のPCで行いました。
- オーディオジャック to オーディオジャックのケーブル
    - 私は [こちら](https://amazon.co.jp/dp/B01FJ9DY90/) の商品を使いました。

# 録音手順
1. ケーブルの一端をカセットコンポのイヤホンジャックに挿します。
1. もう一端をWindowsパソコンのマイクジャックに挿します。
1. Windows11にプリインストールされているサウンドレコーダーを起動します。
1. サウンドレコーダーのマイク設定が手順2で接続した出力に合っていることを確認します。
1. サウンドレコーダーの `設定 > レコーディング > レコーディング形式` で MP3 を選択します。。
1. カセットコンポでテープを再生します。
1. サウンドレコーダーの録音ボタンをクリックし録音を開始します。
1. 任意のタイミングでサウンドレコーダーの録音停止を行います。

# 録音データを Audacity で簡単な加工
Audacity で簡単な加工を行います。
1. Audacity を起動し録音ファイルをD&D。
1. 不要な先頭部分と末尾部分をカットします。
1. 好きなファイル名で保存します。
    - 保存時にアーティスト名等を入れておくとカーナビ等に入れるときに便利です。

# まとめ
無事にMP3化出来ました。
サウンドレコーダーでは他にもAACやWAVなどでも保存できるので自分に合った形式で保存してください。

# 補足
Windows10 のボイスレコーダーというアプリケーションでも同様のことが出来ます。
AWS/EC2に向けてSSHを利用したshellscriptでpingっぽく疎通確認する

AWSのEC2はデフォルトだとpingによる疎通確認が出来ません。

AWS Security Groupのデフォルト設定ではSSH(TCP22)しか空いていないためです。
そこで、SSHを利用したshellscriptでpingっぽい確認を使用と思います。

# ■やりたいこと
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/201787/5dd55862-baac-dfe8-148a-d608da1ed0f2.png)


# ■スクリプト

```bash:ssh-ping
#!/bin/bash

COUNT=1

while true
do
  echo "Try:" $COUNT
  date

  timeout 1 ssh 11.22.33.44

  date
  echo #

  COUNT=`expr $COUNT + 1`
done
```
※無限ループするため、終了したい場合はctrl+cで停止ください。
※10行目のIPアドレスについては適宜書き換えてください。


## ◇解説
EC2にSSHログインするためにはプライベートキー(.pem証明書)が必要となります。
ですが、その証明書を指定しない場合は以下のエラーとなるため、こちらを通信応答として利用します。
よって10行目のsshコマンドにおいて、敢えて証明書オプション等を付けずに実行します。

```text:sshエラー
Permission denied (publickey,gssapi-keyex,gssapi-with-mic).
```

<br>
また、EC2へ通信できない場合は20秒後に以下のエラーとなります。さすがに20秒はちょっと待てないので、10行目のsshコマンドを1秒でtimeoutしています。

```text:無応答エラー
ssh: connect to host 11.22.33.44 port 22: Connection refused
```





# ■結果
正常時は`Permission denied`の応答が表示されます。

```text:result(正常時)
Try: 1
2022年 10月 13日 木曜日 18:27:09 JST
Permission denied (publickey,gssapi-keyex,gssapi-with-mic).
2022年 10月 13日 木曜日 18:27:09 JST

Try: 2
2022年 10月 13日 木曜日 18:27:09 JST
Permission denied (publickey,gssapi-keyex,gssapi-with-mic).
2022年 10月 13日 木曜日 18:27:09 JST

Try: 3
2022年 10月 13日 木曜日 18:27:09 JST
Permission denied (publickey,gssapi-keyex,gssapi-with-mic).
2022年 10月 13日 木曜日 18:27:09 JST
```

異常時は`Permission denied`が応答されないことで見分けます。

```text:result(通信断を検知)
Try: 80
2022年 10月 13日 木曜日 18:30:54 JST
Permission denied (publickey,gssapi-keyex,gssapi-with-mic).
2022年 10月 13日 木曜日 18:30:55 JST

Try: 81
2022年 10月 13日 木曜日 18:30:55 JST
2022年 10月 13日 木曜日 18:30:56 JST

Try: 82
2022年 10月 13日 木曜日 18:30:56 JST
2022年 10月 13日 木曜日 18:30:57 JST

Try: 83
2022年 10月 13日 木曜日 18:30:57 JST
2022年 10月 13日 木曜日 18:30:58 JST


（中略）


Try: 98
2022年 10月 13日 木曜日 18:31:12 JST
2022年 10月 13日 木曜日 18:31:13 JST

Try: 99
2022年 10月 13日 木曜日 18:31:13 JST
Permission denied (publickey,gssapi-keyex,gssapi-with-mic).
2022年 10月 13日 木曜日 18:31:13 JST
```
※Try81～98あたりでAWS Security Groupで通信制限を行いました


<br>
<br>
＃もうちょっと出力結果とか色々きれいにできそうですが、シンプルが一番ということで何もしていません。気になる方はお好きにカスタムしてみてください。


# ■参考
SSH を使用した Linux インスタンスへの接続
https://docs.aws.amazon.com/ja_jp/AWSEC2/latest/UserGuide/AccessingInstancesLinux.html
SQLServerでテーブルごとの統計情報の最終更新日時を取得したい

SQLServerでテーブルごとの統計情報の最終更新日時を取得したい場合、`sys.stats`テーブルと`STATS_DATE`関数を活用します。

たとえば`EMP`テーブルと`DEPT`テーブルの統計情報の最終更新日時を取得したい場合は以下のようなSQLで実現できます。

```sql
SELECT 
  OBJECT_NAME(object_id) AS table_name,
  name AS index_name,
  STATS_DATE(object_id, stats_id) AS last_updated
FROM sys.stats
WHERE OBJECT_NAME(object_id) IN ('EMP', 'DEPT')
```

このSQLを実行すると、以下のような結果を得ることができるはずです。

|table_name|index_name|last_updated|
|:---|:---|:---|
|EMP|PK_EMP_001|2022/10/29 12:15:08.157|
|EMP|IX_EMP_001|2022/10/29 12:15:08.163|
|EMP|IX_EMP_002|2022/10/29 12:15:08.167|
|EMP|IX_EMP_003|2022/10/29 12:15:08.173|
|DEPT|PK_DEPT_001|2022/10/29 12:15:08.190|
|DEPT|IX_DEPT_001|2022/10/29 12:15:08.200|
|DEPT|IX_DEPT_002|2022/10/29 12:15:08.203|

**環境情報**

```
Microsoft SQL Server 2019 (RTM-CU9) (KB5000642) - 15.0.4102.2 (X64) 
	Jan 25 2021 20:16:12 
	Copyright (C) 2019 Microsoft Corporation
	Developer Edition (64-bit) on Linux (Ubuntu 18.04.5 LTS) <X64>
```
nodenvをインストールする (Ubuntu)

記載日：2022/10/29
更新日：2024/01/26

Ubuntuにnodenvをインストールする方法を記載します。

#### 目標
ubuntuにnodenvをインストールし、希望するバージョンのnodeコマンドを実行できるようにする。

#### 環境
OS : Ubuntu22.04
nodenv : 1.4.0
gitインストール済

#### 作業 (約10分)
#### 0.nodenvライブラリのダウンロード (5分)
Terminalを開き、以下コマンドを実行。
（コマンド実行後、ホームディレクトリで”ll -a”コマンドを実行すると.nodenvが追加されていて、ダウンロードできていることが確認できる。）

~~~command
git clone https://github.com/nodenv/nodenv.git ~/.nodenv
~~~

nodenvコマンドを実行できるようにするためにパスの設定を行う。.bashrcファイルに記述追加するために、以下コマンドを実行する。
.bashrc以外に、.profileなどにもパス設定を追加したい方は追加する。

~~~command
echo 'export PATH="$HOME/.nodenv/bin:$PATH"' >> ~/.bashrc
echo 'eval "$(nodenv init -)"' >> ~/.bashrc
~~~

nodejsのバージョン一覧を取得するにはnode-buildもインストールする必要がある。
~~~command
cd ~
git clone https://github.com/nodenv/node-build.git $(nodenv root)/plugins/node-build

# あるいは、.nodenv下にpluginsディレクトリを作ってから、そこにgit cloneしても良い
cd ~/.nodenv
sudo mkdir plugins
cd ./plugins
git clone https://github.com/nodenv/node-build.git
~~~

変更内容を読み込ませる。

~~~command
source .bashrc
~~~

ターミナルで、以下コマンドを実行するとnodenvのバージョンが表示され、インストールできたのが確認できる

~~~command
nodenv --version
~~~


#### 1.nodenvでnodeをインストール (5分)
以下コマンドを実行し、インストールできるnodeバージョンを確認する。
~~~command
nodenv install -list
~~~

希望のバージョンをインストールする。
~~~command
nodenv install 18.9.0
~~~

対象バージョンがインストールできたことを以下コマンドで確認。
~~~command
nodenv versions
~~~

グローバルでnodeコマンドを使えるようにする。
~~~command
nodenv global 18.9.0
~~~

最後にnodeコマンドが使えることを確認。
~~~command
node --version
~~~

#### 参考にさせて頂いたサイト
・nodenvについて
https://blog.mintsu-dev.com/posts/2020-07-22-install-nodenv-linux/
・nodenvのgithub
https://github.com/nodenv/nodenv
【Rails6 & Docker】yarnのバージョンが古いとエラーが出た場合にしたこと

## Dockerでrails6の環境を構築した。
以下の記述でrails6の環境を立ち上げました
```
# Dockerfile
FROM ruby:2.6.5

RUN apt-get update && apt-get install -y sqlite3 build-essential libsqlite3-dev
RUN curl https://deb.nodesource.com/setup_18.x | bash
RUN curl https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add -
RUN echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list

RUN apt update && apt install -y nodejs yarn

RUN mkdir /app_name 

ENV APP_ROOT /app_name 
WORKDIR $APP_ROOT

ADD ./Gemfile $APP_ROOT/Gemfile
ADD ./Gemfile.lock $APP_ROOT/Gemfile.lock

RUN bundle install
RUN rails webpacker:install
RUN rails webpacker:compile
RUN rails db:migrate
ADD . $APP_ROOT
```
```
# docker-compose.yml
version: '3'
services:
  web:
    build: .
    command: /bin/sh -c "rm -f tmp/pids/server.pid && bundle exec rails s -p 3000 -b '0.0.0.0'"
    # docker-compose killとかでコンテナ終了させたとき、pidファイルが残っているので削除
    stdin_open: true
    tty: true
    # 上記二文にてbyebugを実行する設定
    # stdin_open:標準入力への接続を表す。
    # tty:標準出力の接続を表す。
    volumes:
      - .:/app_name
    ports:
      - "3000:3000"
```

docker-compose build 実行完了後 docker-compose up でサーバーを立ち上げようとするとエラーが発生しました。
```
error Couldn't find an integrity file                                                        
error Found 1 errors.                                                                        


========================================
  Your Yarn packages are out of date!
  Please run `yarn install --check-files` to update.
========================================


To disable this check, please change `check_yarn_integrity`
to `false` in your webpacker config file (config/webpacker.yml).


yarn check v1.22.19
info Visit https://yarnpkg.com/en/docs/cli/check for documentation about this command.
```
翻訳してみるとYarnのバージョンが古くなっているらしいので以下のコマンドでアップデートしました。
```
yarn install # 自分はdockerなので "docker-compose run web yarn install"で実行
```
実行後、インストールは正しく行えたが、その後のbuildでエラーが発生
```
~省略~
make: *** [binding.target.mk:133: Release/obj.target/binding/src/binding.o] Error 1
make: Leaving directory '/app_name/node_modules/node-sass/build'
gyp ERR! build error 
gyp ERR! stack Error: `make` failed with exit code: 2
gyp ERR! stack     at ChildProcess.onExit (/app_name/node_modules/node-gyp/lib/build.js:262:23)
gyp ERR! stack     at ChildProcess.emit (node:events:513:28)
gyp ERR! stack     at ChildProcess._handle.onexit (node:internal/child_process:291:12)
gyp ERR! System Linux 5.10.16.3-microsoft-standard-WSL2
gyp ERR! command "/usr/bin/node" "/app_name/node_modules/node-gyp/bin/node-gyp.js" "rebuild" "--verbose" "--libsass_ext=" "--libsass_cflags=" "--libsass_ldflags=" "--libsass_library="
gyp ERR! cwd /app_name/node_modules/node-sass
gyp ERR! node -v v18.12.0
gyp ERR! node-gyp -v v3.8.0
gyp ERR! not ok 
```
初めて出るエラーがだったので、検索してみた結果以下のサイトがヒットしました。
https://blog.furu07yu.com/entry/yarn-gyp-err

どうやら、下から4行目にあるようにnode-sassのバージョンに関する問題でエラーが発生している模様
そのため、サイト様に書かれているように"packerage.json"に書かれている@rails/webpackerのバージョンを修正したところ無事解消されました。

```
"@rails/webpacker": "^5.4.3",
```
また、gemとバージョンは合わせた方が良いかと考えGemfileに記載されているwebpackerのバージョンも更新しました。
```
gem 'webpacker', '~> 5.0'
```
docker: Error response from daemon: Ports are not available: listen tcp 0.0.0.0:80: bind: address already in use.

# 環境

|対象|Version|
|---|---|
|PC|MacBook Air（M1, 2020）|
|OS|macOS Monterey|
|Docker|20.10.12|


# 原因
既に80番ポートを使用しているプロセスが存在していた。

# 対策 
**結論：80番ポートで実行しているプロセスを終了させる。**
1. 80番ポートで実行しているプロセスのPIDを明確にする
`sudo lsof -i -P | grep :80`を実行して確認。
![Screen Shot 2022-10-29 at 21.03.46.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2950485/2ae074b0-364d-1bfa-8c84-5cee32925dbe.png)
今回は2235, 2239が対象のPIDだと分かる
2. プロセスをkillする
`kill [PID]`を実行する。今回であれば`kill 2235`, `kill 2239`を実行する
**本来はここまでで十分なはず。**
しかし私の環境では`kill [pid]`を実行してもすぐにプロセスが再度実行されてしまった。
3. （オプション）プロセスについて詳しく調べる
`ps axj`を実行すると、以下結果が返ってきた。 
![Screen Shot 2022-10-29 at 20.59.04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2950485/919909d3-5c4b-69ef-74b6-af6b99f69765.png)
80番ポートで実行されていたプロセスは`/usr/sbin/httpd -D FOREGROUND`だとわかる。
FOREGROUNDと付いているのも気になるが、そもそもこのコマンドは何かを調べるとapache httpdと呼ばれるものだと分かった。
4. （オプション）apacheとしてプロセスを終了させる
結論、`/usr/sbin/apachectl stop`の実行で無事プロセスは終了した。
Ubuntu(wsl)へのMySQL(8)セットアップ

MySQL触ってみよう！ということで、[ここ](https://learn.microsoft.com/ja-jp/windows/wsl/tutorials/wsl-database)を見ながらMySQLをセットアップしていたらインストール後のセットアップで困りました。

ググって対応策を試しても解決できず困っていたのですが、公式を見に行ったら即解決しました。
StackOverflowなど便利なサイトは多いですが、公式のドキュメントを見るのが一番ですね。なのでこの記事を見る必要はないのですが、同じように詰まった人や数か月もしたら忘れる自分のために残しておきます。

## 結論
[公式のドキュメンテーション](https://dev.mysql.com/doc/refman/8.0/ja/installing.html)を読もう。
以降詳細なインストールの流れと自分の詰まったところをメモっておきます。

## インストール
クライアント、プログラムをaptからパッケージ経由でインストール。
```shell
sudo apt-get install mysql-server
sudo apt-get install mysql-client
```
これでMySQL8.0サーバー、クライアントがインストールされるので実行は可能となる。

## セットアップ
まずサーバー起動
```shell
sudo service mysql start
```
ここで
```shell
sudo mysql_secure_installation
```
でrootパスワード設定など諸々の設定ができるように書かれていたが、ここでパスワードが必要。
```shell-session
Error: Access denied for user 'root'@'localhost' (using password: YES)
```
しっかり怒られます。
これをググるとセーフモードで～等、過去の解決法が出てくるが、セーフモードではパスワード変更ができません。
解決法を探しに行きます。

https://dev.mysql.com/doc/refman/8.0/ja/data-directory-initialization.html

初期化はすでに行われているので必要ありませんが、パスワード周りを再設定したいので大人しく従います。
mysql.userテーブルを変にいじって困っているひともリセットボタンで解決です。

まずはmysqldコマンドを使い、データディレクトリを初期化します。
ただし初期化の前にデータディレクトリ以下を全削除します。(データディレクトリが空でないと実行されないため)
(自分は/var/lib/mysqlがデータディレクトリでしたが、異なる場合は動いているmysqldプロセスのオプションを見ればわかると思います。)

サーバー実行している場合は一度停止
```shell
service mysql stop
```
初期化
```shell
su
cd /var/lib/mysql
rm -r ./*
mysqld --initialize-insecure --user=mysql
```
これで/var/lib/mysql以下が再び生成され、パスなしでrootに入れる状態になります。
>--initialize-insecure を使用してデータディレクトリを初期化した場合は、パスワードなしで root としてサーバーに接続します:
>```shell
>service mysql start
>mysql -u -root --skip-password
>```
>接続後、ALTER USER ステートメントを使用して新しい root パスワードを割り当てます:
>```sql
>ALTER USER 'root'@'localhost' IDENTIFIED BY 'root-password';
>```
これでパスワードが設定されたので以降パスワードでrootに入れます。

## 一般ユーザーでmysql -u root -pできないんだけど！！
自分はubuntuの一般ユーザーでmysqlに入ろうとすると怒られました。
```
$ mysql -u root -p
Enter password:
ERROR 2002 (HY000): Can't connect to local MySQL server through socket '/var/run/mysqld/mysqld.sock' (13)
```
ソケット経由で繋げないのはわかったけど、ちゃんとソケットあるし、何が悪いんだ・・・・

perrorというコマンドでエラーコードを調べられるそうです。

https://dev.mysql.com/doc/refman/8.0/ja/perror.html

```
$ perror 13
OS error code  13:  Permission denied
MySQL error code MY-000013: Can't get stat of '%s' (OS errno %d - %s)
```
どうやらエラー末尾の(13)はパーミッション周りのエラーのようです。
```
$ ls -l /var/run/mysqld/mysqld.sock
srwxrwxrwx 1 mysql mysql /var/run/mysqld/mysqld.sock
```
自分はmysqld.sockのパーミッションはokでしたが、ディレクトリが読めなくなっていました
```
$ ls -l /var/run/
srwx------ 1 mysql mysql /var/run/mysqld
$ chmod 755 /var/run/mysqld
```
無事解決。

## パスワードを忘れた
無いと思いますがパスワードのみリセットする方法がここにあります。
ディレクトリの初期化方法を上に書きましたが、実はこちらでもok

https://dev.mysql.com/doc/refman/8.0/ja/resetting-permissions.html
Raspberry Pi 4BをWi-Fiアクセスポイントにしたのに接続できない（小ネタ）

# はじめに
Raspberry Pi 4BをWi-Fiルーターにしてうまく動いたのですが、Win11から接続しようとしたときにうまく接続できなかったので確認したら通信が暗号化されてなかったのが原因らしいです。

症状としては、PCからSSIDは見えているのに接続しようとすると接続できない、診断しても原因不明としか答えてくれない状況です。
他のRPiやちょっと古いandroidスマホからなら問題なく接続できます。

# 解決策
Raspberry Piの /etc/hostapd/hostapd/conf に、暗号化方式の指定を入れるだけです。
```
wpa_pairwise=CCMP #WPAの暗号化方式、
rsn_pairwise=CCMP #WPA2の暗号化方式を指定する。
```
参考にしたサイトの設定例にこの設定が抜けていたので気づきませんでした。

こちらのサイトがとても詳細に説明してくれていたので助かりました。
https://kassyjp.ninja-web.net/ras/jessie/bridge.htm
ITミステリー短編：消えたエンジニア

## プロローグ

営業A「いい感じのIT人材いないっすね。」
営業B「30代ぐらいの現場経験が豊富でリーダできるやつほしいよね。」
営業A「そんなやつは、みんな条件がいいところに引っ張られてるだろ？」

営業A
(…まてよ？…こんな話、10年以上していないか…？)

…IT業界の登場人物がサザエさんやポケモンのサトシの様に年を取らないなら、この仮説は合っている可能性もあるだろう。だが、IT業界に従事している人々は**年を取を取っている**。DXやらなにやらで必要な人材の数が増えているにしても、何かがおかしい。

「これは調査する必要がありそうだな。」
そうつぶやくと営業Aはパソコンに向かってググりはじめた。


## IT人材年齢分布の推移

営業A「クソ…悪い予感が的中したか」
営業Aは経産省のレポートを眺めながらそうつぶやいた。

![IT人材の年齢分布の推移.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/136165/3648df33-9eb8-4410-a0f3-d0a323d8fe14.png)

出典: 経済産業省 [－ IT 人材需給に関する調査 －](https://www.meti.go.jp/policy/it_policy/jinzai/houkokusyo.pdf)

グラフの見方はおそらくこうだろう。
順当に推移しているならば、2010年や2015年の25歳の割合が、2015年や2020年の30歳の割合におよそ移動するはず。

ところがグラフを見ると、2010年から2020年で、25 - 29歳と、30 - 34歳の推移の仕方に大きな違いがあり、25- 29歳が微減に対して、30 -34 歳は2010年から2020年にかけて半減しているじゃあないか。

これは条件がいいところに引っ張られていると考えるより、どこへともなく消えていると考える方が自然なんじゃないのか…？


## プログラマー35歳定年説（再）

営業A「こんな未来予想図になるのか…？」

グラフの2025年と2030年の推計は予測値とある。ITスクールや商材ビジネスの広告が世に溢れ、未経験エンジニアも大歓迎だ。それなら確かにIT人材は増えるかもしれない。

それにしても、20代は横ばいに対して、30代が増加傾向になるだろうか。何らかの理由で半減する程離脱しているのに、誰かに数ヶ月教えてもらった技術を片手に参入するのだろうか。

30歳を超えた時点で仕事の質が変わる、それまでの仕事の仕方が通用しなくなる、新規技術が習得できなくなるなど理由で離脱する、というグラフじゃないのかこれは？

『プログラマー35歳定年説』は都市伝説じゃなかったのか…？


## プレイヤーからプレイングマネージャへ

営業A「そんな冗談は思考を鈍らせるな…」

『プログラマー35歳定年説』の邪念を払うかのように大きく首を振ったところ、新たな仮説が頭をよぎった。

「そうか…プレマネか」

SESの人月労働者は時給労働者だ。単位時間内でどんなに素晴らしいコードを書いても顧客からもらえるお金は変わらない。顧客からもらえるお金を増やそうと思ったら、雇ってもらう人を増やすしかない。

1人月分のアウトプットを出せるか分からない新人をつけられて、自分と合わせて2人月分のアウトプットを出さなければいけなくなる。会社からリーダーとかいうキラキラした言葉で圧をかけられはじめる年齢が30歳ぐらいからと考えると辻褄が合う。

PGとしての仕事も覚えて、だいたい作業見積もりもできるようになり定時退社余裕となった矢先に、未知との遭遇だ。

プレマネの業務は、PG能力はベースとしてもちろん必要な上に、それ以外の能力も多く要求される。プレイヤーではなくプレイングマネージャにある日突然させられる。その準備がまったくできていないならば、昨日までは自分の仕事が完璧にできていたのに、今日からは全くできない。何のサポートもなく責任だけは増えるリーダー。離脱してしまうのは自然だろう。


## 火消し屋

グラフから読み取れることがもう一つある。

少し前であれば、40代、50代のエンジニアは書類の時点で敬遠されがちだった。
ところが、30代は離脱してしまい炎上案件を鎮火できるのが40代以上だけとなると、40代からでもしばらくは仕事はあることになる。その人々が一斉に定年を迎えると火消しができなくなるかもしれないが、それまでに炎上プロジェクトはなくなっているのだろうか。


## エピローグ

IT人材不足なのは間違いない。
未経験30代だろうと、40代だろうとIT業界を目指す人はウエルカムだ。

以下のような業務ができるのであれば営業Aに声をかけるといいだろう。

- 顧客から要望ヒアリング
- 要件・仕様整理・調整
- 仕様書作成
- タスク分割・スケジュール作成・人員割当
- 指示出し・進捗確認
- 開発・試験
- 保守

求人でよく見かける「コミュニケーション能力」をより明確に書くと「顧客の要望を理解・整理して現場の開発者が作業できるように落とし込んで口頭および文書でやりとり」できる能力ということになるだろう。

「リモワで楽しくキーボードだけを叩いて高収入ゲットの案件ばかりならIT人材が不足などしないはずだが・・・難しいものだな。」

そうつぶやいて営業Aはパソコンを閉じた。


## おまけ1

営業A短編シリーズ

https://qiita.com/kaku3/items/3378ea55b1240d7360a1


## おまけ2

IT人材不足を憂いて、学習用の資料をgitで**無料**公開してます(不定期更新)。
よろしければご利用ください。
[エンジニアのためのお仕事問題集](https://github.com/kaku3/workbooks)
paiza.ioでelixir その24

# 概要
paiza.ioでelixirやってみた。
defmacro使ってみた。

# 参考にしたページ

https://qiita.com/yasuhiro-okada-aktsk/items/742f266329960ca0ea20

# サンプルコード

```
defmodule Sample0 do
	defmacro test() do
		{:hello, [], nil}
	end
end
defmodule Main do
	require Sample0
	import Sample0
	def hello() do
	    IO.puts "Hello World!!"
	end
	def main() do
		test()
	end
end

Main.main



```

# 実行結果

```
Hello World!!
```

# 成果物

https://paiza.io/projects/J9YeeTbWE9QsDI47piFI6w

以上。
【Swift】MintでSwiftLintを導入する

# はじめに
SwiftLintを初めて使ってみたのでやりかたを記録しておきます

# やりかた
### Mintfile
```:ターミナル
cd プロジェクトフォルダ
```
Mintfileを作成します
```:ターミナル
touch Mintfile
```
Mintfileを開きます
```:ターミナル
open Mintfile
```
MintfileにLicensePlistを記載します
```:Mintfile
realm/SwiftLint@0.49.1
```
SwiftLintをインストールします
```:ターミナル
mint bootstrap
```
### .swiftlint.yml
.swiftlint.ymlを作成します
```:ターミナル
touch .swiftlint.yml
```
.swiftlint.ymlを開きます
```:ターミナル
open .swiftlint.yml
```
.swiftlint.ymlを編集します
他にもたくさんルールがあるので[公式のルール一覧](https://realm.github.io/SwiftLint/rule-directory.html)を見てください
```.swiftlint.yml
# 無効にするルール
disabled_rules:
- function_body_length
- line_length
- trailing_whitespace

# チェック対象ファイル
included:
- {プロジェクト直下のファイル名}

# Class, Struct, Enumの行数制限
type_body_length:
- 400
- 600

# 1ファイルの行数制限
file_length:
- 600
- 1000
```
### Run Script
```sh
export PATH=/opt/homebrew/bin:$PATH

if which mint >/dev/null; then
    # SwiftLint
    xcrun --sdk macosx mint run swiftlint
else 
    echo 'warning: mint not installed.'
fi
```

# おわり
SwiftLintは難しそうで避けていましたが、意外と簡単でびっくりしました。
社内Wiki検討（Notepm・Growi・Qiita）メモ

# 概要

社内で技術的なナレッジを共有するツールの選定をしたので、そのメモ。
他にもツールはあるけれど、社内で候補となったものでの比較です。


# 各ツールの特徴・感想

前提条件などが全然違うので個別の感想を記載。
特に記載していない機能（タグ・ブックマーク・コメント・検索等）は特に差を感じなかったです。

## Notepm

- **画像を直接編集可能**
  - 別途編集してから貼り付けなくてよくて便利。
- Wordをアップロード可能
  - Markdown との互換性が低く、期待外れ。
    - せめてアウトラインだけでも互換性を持ってほしかった。
- 管理系機能の充実
  - レポート出力
    - ちゃんと分析するかはともかく。
- 外部共有も可能
    - URL発行して共有するタイプ。
- **値段が高い**
  - 閲覧ユーザーが編集ユーザーの3倍、という制約が微妙。
    - そもそも、みなで編集していくのがWikiでは？



## Growi

クラウド版ではなく、オンプレ版です。

- 社内構築済み
  - 別途ポータルサイトとして利用中。
  - LDAP連携済み
- 画像生成機能
  - drow.io
  - **PlantUML**
      - VSCodeのPlantUMLプラグインで画像作成→画像を貼り付けをしなくてよく、直接編集できるのは便利。
      - VSCodeにセットアップする場合、Graphviz等も必要なので、その手間を省ける点も便利。


## Qiita

- そもそも社内より社外向け
  - 限定公開機能で部分的には可能
  - 逆に、社外向けに広報したい場合に便利
- **個人に帰属する**
  - Organizationosの機能はあるものの、記事は個人に帰属するため、社内ナレッジとしてどうか？
- ドキュメント共有基盤として利用不可
- Markdown の編集補足はない。
  - 自分の使いやすいエディタで記載してから貼り付ける等できるので、重要度は低い。


# 終わりに

- 今回は既にオンプレ構築済みという点で、Growiの優位性が高いか。
- 機能面でも、PlantUMLがデフォルトで利用できるのでGrowi推し。
- Notepmはコスト面で厳しい印象。
  - ユーザー数に比例してどこまでもコストが上がっていくので、途中でユーザー数制限がなくなるGrowi Cloudと比較すると…
  - 外部共有機能を使えば、閲覧ユーザーとしてカウントされないとは言え、編集できないのはちょっと…
- 会社でQiitaを利用する場合は、制度・文化の整理が重要か。


# 参考


https://notepm.jp/

https://growi.org/ja/

https://qiita.com/couzie/items/9dedb834c5aff09ea7b2
🔰超初心者のこの先の超初心者の皆様に向けて#3

*データ型と型推論
・型とは？（代表的な一部です）
Int型（整数）とは整数値を入れる型のこと
例： var myAge: Int = 30
Double型（小数点）とは小数点の数値を入れる型のこと
例：　var height: Double = 185.5
String型（文字列）とは文字列を表す型のこと
例： let myName: string = "ayumu"

上記みたいに数値や文字によって使用される型がある。
ayumuは自分の名前ですが"ayumu"は数値ではないのでString型を使用して表すルールがあると覚えています。

＊型推論とは？
例：let myName: String = "ayumu"が正式な型ですが
　　let maName = "ayumu"にしてもコンパイル時にString型
　　と推測してくれる。