#TIL #elixirlang 업데이트 가능한 패키지를 찾기 덤으로 버전 간 변경 사항까지 확인

프로젝트에 쓰는 패키지 중 버전이 올라간 패키지가 있을까?

$ mix hex.outdated

Dependency                    Current     Latest  Update possible
dialyxir                      1.0.0-rc.3  1.0.0   Yes
httpoison                     1.1.1       1.6.2   No
jason                         1.1.2       1.2.0   Yes
logger_file_backend           0.0.10      0.0.11  Yes
prometheus_ex                 3.0.5       3.0.5
prometheus_httpd              2.1.11      2.1.11
prometheus_process_collector  1.4.5       1.4.5
sentry                        7.2.1       7.2.4   Yes
slack                         0.14.0      0.23.2  No
telemetry                     0.4.1       0.4.1
timex                         3.3.0       3.6.1   Yes

A green version in latest means you have the latest version of a given package, a red version means there is a newer version available. Update possible indicates if your current requirement matches the latest version.
Run `mix hex.outdated APP` to see requirements for a specific dependency.

hex.outdated 옵션을 사용하면 최신 버전과 업그레이드가 가능한지 확인할 수 있다.

$ mix deps.update dialyxir jason logger_file_backend sentry timex

업데이트는 deps.update 옵션을 사용한다. 패키지 이름을 적으면 된다.

$ mix deps.update --all

전체 업데이트는 --all 옵션을 사용한다.

$ mix hex.outdated slack
There is newer version of the dependency available 0.23.2 > 0.14.0!

Source   Requirement
mix.exs  ~> 0.14.0

A green requirement means that it matches the latest version, a red requirement means that that it does not match the latest version.

hex.outdated 옵션 뒤에 패키지 이름을 넘기면 업데이트가 안 되는 이유를 알 수 있다. slack 패키지는 mix.exs 파일에 마이너 버전이 14를 넘지 않게 정의해서 업데이트가 안 된다.

$ mix hex.package diff httpoison 1.1.1..1.6.2

hex.package diff 옵션을 사용해 패키지 버전 사이에 변경 사항을 볼 수도 있다.


#TIL #git commit을 포함하는 branch를 알고 싶을 때

이번에 배포한 브랜치를 언제 땄더라. a 커밋이 이번에 배포한 브랜치에 포함되었나?

$ git branch -r --contains COMMIT_ID
  origin/0.0.9
  origin/0.0.10
  origin/0.1.0
  origin/0.1.1

--contains 옵션을 사용하면 나온다. 리모트 브랜치까지 출력하고 싶으면 -r 옵션을 추가한다.

참고: How to list branches that contain a given commit? - stackoverflow.com


#TIL #docker 컨테이너에서 host로 접속은 어떻게 해야할까?

프로메테우스(prometheus)와 그라파나(grafana)를 테스트하고 싶다. 간편하게 docker를 사용해서 띄우고 host에서 실행하는 웹프레임워크 메트릭을 보고 싶다.

docker 컨테이너에서 host로 연결할 수 있을까? host ip는 어떻게 구해야 할까?

The host has a changing IP address (or none if you have no network access). From 18.03 onwards our recommendation is to connect to the special DNS name host.docker.internal, which resolves to the internal IP address used by the host. This is for development purpose and will not work in a production environment outside of Docker Desktop for Windows.

- Networking features in Docker Desktop for Windows

host.docker.internal dns 이름을 사용하면 컨테이너에서 host로 접속할 수 있다.

scrape_configs:
- job_name: hello_metrics
  honor_timestamps: true
  scrape_interval: 15s
  scrape_timeout: 10s
  metrics_path: /metrics
  scheme: http
  static_configs:
  - targets:
    - host.docker.internal:4000

docker 컨테이너로 띄운 프로메테우스에서 host.docker.internal dns 이름을 사용해 host의 메트릭을 긁어오게 설정했다.


#TIL #svn #linux #macos 파일 실행 권한을 svn 프로퍼티로 적용

Subversion provides the svn:executable property as a way to specify that the executable bit for the file on which that property is set should be enabled, and Subversion honors that request when populating working copies with such files.

- File Executability - svnbook.red-bean.com

linux에서 svn을 처음 써 본 적이 없어서 이런 게 있는지도 몰랐다. git과 다르게 svn:executable 속성을 세팅해야 체크아웃할 때, 파일 실행 권한을 svn이 세팅해준다.

$ find . -executable -type f | xargs svn propset svn:executable ON

linux에서 add하고 커밋하기 전에 이런 명령을 실행하면 로컬 파일의 실행 권한과 svn 파일 실행 권한을 똑같이 맞춰준다.

$ find . -perm +111 -type f | xargs svn propset svn:executable ON

macOS는 인자를 다르게 해야 한다.

참고


#TIL #bash 파일 이름과 디렉터리 이름 중 가장 높은 버전을 구하는 법

$ ls | sort --version-sort --reverse | grep -Po [0-9]+\\.[0-9]+\\.[0-9]+ | head -n 1

이렇게 입력하면 파일 이름이나 디렉터리 이름 중 가장 높은 버전을 출력한다.

sort 프로그램의 --version-sort 옵션을 사용하면 알파벳순 정렬이 아니라 버전 정렬을 사용한다. 예를 들면 1, 120, 2, … 순서로 정렬하는 게 아니라 1, 2, 3, 120, … 순서로 정렬한다. grep 프로그램의 -Po 옵션을 사용해서 + 매칭 조건을 쓸 수 있게 하고 일치하는 부분만 출력한다. 즉 0.0.1a가 있으면 0.0.1만 출력한다.

참고


#TIL #git Should I try again? 물어보지 않게 하기

$ git pull
...
Unlink of file 'xxxx' failed. Should I try again? (y/n)

가끔 이런 메시지가 뜬다. gc가 자동으로 실행되면서 이런 게 뜨는데, unlink 실패하는 파일이 한두 개가 아니라서 수동으로 n 을 입력하면 끝도 없다.

$ export GIT_ASK_YESNO=false

y/n 을 입력하는 프롬프트(prompt)가 안 뜨게 한다. no로 처리하는 것 같다.

왜 unlink가 실패하는 걸까? 다른 프로그램에서 파일을 사용 중이라 실패하는 것 같다. git 저장소 무결성이 깨지는 게 아니라 unlink 실패 메시지가 뜨더라도 신경 쓰지 않는다.

참고 - Unlink of file Failed. Should I try again? - stackoverflow.com


#TIL #bash 특정 파일 패턴을 제외하고 모두 삭제

$ find . -type f ! -name '*.txt'  -delete

현재 디렉터리에서 *.txt 패턴에 일치하지 않는 파일을 지운다.

The first part of the expression is recognised by the fact that it begins with ‘-’ followed by some other letters (for example ‘-print’), or is either ‘(’ or ‘!’. Any arguments after it are the rest of the expression.

- 8.1 Invoking find - gnu.org

! 연산자를 사용할 수 있다. ! -name ‘*.txt’ 으로 쓰면 *.txt 패턴이 아닌 이름을 찾는다.

-delete 옵션을 사용해 찾은 파일을 지운다.

참고 - In linux, how to delete all files EXCEPT the pattern *.txt? - unix.stackexchange.com


#TIL #linux #awk csv 특정 컬럼 값을 0으로 바꾸기

$ cat sample.csv
1,2,3,4,5
a,b,c,d,e
A,B,C,D,E

이런 파일이 있다. 두 번째 컬럼 값만 0으로 바꾸고 싶다면 어떻게 해야 할까?

$ awk 'BEGIN{FS=OFS=","} {$2=0}'1 sample.csv
1,0,3,4,5
a,0,c,d,e
A,0,C,D,E

awk 짱이시다. 쓸데마다 찾아본다. 복잡한 인자. 아직 친해지려면 멀었다. 우선 awk에서 쓰는 용어 정리부터 하자. 룰(rule), 패턴(pattern), 액션(action)이란 용어를 사용한다. 룰은 pattern { action } 문법으로 구성한다.

BEGIN 패턴은 입력 처리 전에 한 번 호출한다. 처리 후에 부르는 END 패턴도 있다.

BEGIN 패턴으로 정의한 FS=OFS=”,” 액션은 입력 필드 분리 기호(separator)와 출력 필드 분리 기호를 설정한다. 둘 다 , 문자를 사용했다.

$ awk 'BEGIN{FS=",";OFS="|"} {$2=0}'1 sample.csv
1|0|3|4|5
a|0|c|d|e
A|0|C|D|E

만약 출력 필드 분리 기호를 | 문자로 한다면 출력할 때, | 문자를 사용해서 필드를 합친다.

패턴 없이 액션만 있는 {$2=0} 룰은 모든 입력을 처리한다. $2=0 액션으로 2번째 필드를 0으로 바꾼다.

그럼 끝에 붙은 1은 뭘까? 현재 라인을 출력하는 액션이다. ’ 문자 왼쪽에 써도 되고 오른쪽에 써도 된다.

$ awk 'BEGIN{FS=OFS=","} {$2=0} {print}' sample.csv

대신에 {print} 액션을 써도 된다.

$ gawk -i inplace 'BEGIN{FS=OFS=","} {$2=0}'1 sample.csv
$ cat sample.csv
1,0,3,4,5
a,0,c,d,e
A,0,C,D,E

파일을 바로 수정하려면 gawk 프로그램을 대신 사용하고 -i inplace 옵션을 추가하면 된다.

참고


#TIL #git 가장 높은 버전의 tag를 checkout

$ git tag --list ‘v0.2*' --sort=-v:refname
v0.2.25
v0.2.24
v0.2.23
v0.2.19
v0.2.18
v0.2.17
v0.2.16
v0.2.15
v0.2.14
v0.2.13
v0.2.12

sort 옵션에 키를 정의할 수 있다. v:refname 키를 사용하면 버전으로 취급해서 정렬해준다. --sort=v:refname 옵션은 오른차순 --sort=-v:refname 옵션은 내림차순으로 정렬한다.

$ latest_version=$(git tag --list ‘v0.2*' --sort=-v:refname | head -1)
$ echo $latest_version
v0.2.25
$ git checkout $latest_version

이렇게 하면 마지막 버전을 받을 수 있다.

참고


#TIL #elixirlang map key, value 패턴 매칭

def put_new(map, key, value) do
  case map do
    %{^key => _value} ->
      map

    %{} ->
      put(map, key, value)

    other ->
      :erlang.error({:badmap, other})
  end
end

#elixirlang 함수 이름이 _new 로 끝나면 뭐다? 글을 쓰면서 Map.put_new/3 구현을 봤다. key를 찾고 key가 있으면 map 리턴. 없다면 추가한 map 리턴. 이런 구현을 예상했다.

예상한 것과 다른 구현이다. %{^key => value} -> 처럼 map의 key, value로 패턴 매칭이 가능한 걸 배웠다.


#TIL #bash 20190131064326_abcdefghijk.exs에서 숫자를 추출하는 방법

$ filename=20190131064326_abcdefghijk.exs
$ echo `expr match "$filename" '\([0-9]*\)'`
20190131064326

숫자_문자열.exs 형식에서 _ 문자 앞에 숫자들만 추출해야 한다. expr match 명령어를 사용해 정규식 매칭을 시도했다.

mac에서 동작을 안 한다. : 문자를 사용하는 거로 바꿔야 한다.

$ echo "$filename" | cut -d'_' -f 1
20190131064326

그러고 보니 구분 문자(delimiter)가 명확하다. _ 문자를 구분 문자로 사용하면 된다. 괜히 어렵게 풀었네. -d 옵션으로 구분 문자를 정의하고 -f 1 옵션으로 나눠진 필드 중 첫 번째 필드를 리턴하게 했다.


#TIL #elixirlang in 커널 매크로

iex> fruit = :orange
:orange
iex> fruit == :orange or fruit == :banana
true

과일이 오랜지 혹은 바나나인지 알고 싶을 때는 or 연산자를 사용해 각각 같은지 비교하면 된다.

iex> fruit = :orange
:orange
iex> fruit in [:orange, :banana]
true

Kernel.in/2 매크로를 사용하면 코드를 더 간단히 할 수 있다.

iex> 1 in 1..5
true
iex> 0 in 1..5
false

오른쪽 인자로 Range 모듈을 사용할 수도 있다


#TIL #elixirlang 찾은 원소에 한번 더 접근해야 한다면 Enum.find_value

iex> Enum.find([1, 2, 3], fn x -> rem(x, 2) == 0 end)
2
iex> Enum.find_value([1, 2, 3], fn x -> rem(x, 2) == 0 end)
true

두 함수 모두 두 번째 인자로 넘긴 함수가 참 같은 값(truthy value)을 리턴하는 원소(element)를 찾는다. elixir에서 참 같은 값(truthy value)는 nil, false가 아닌 모든 값이다.

Enum.find/3 함수는 원소를 리턴하는데 반해 Enum.find_value/3 함수는 원소를 찾는데 사용한 함수 리턴 값을 리턴한다. 그래서 첫번째 리턴 값은 2고 두번째 리턴 값은 true다.

[%{fruit: :orange, count: 1}, %{fruit: :banana, count: 2}]
|> Enum.find_value(fn
  %{fruit: :orange, count: c} -> c
  _ -> nil
end)

찾은 원소에서 다시 한번 더 값을 가져와야 할 때, 편하게 사용할 수 있다.

[%{fruit: :orange, count: 1}, %{fruit: :banana, count: 2}]
|> Enum.find(fn x -> x.fruit == :orange end)
|> case do
  %{count: c} -> c
  _ -> nil
end

Enum.find/3 함수를 사용하면 찾은 다음 원소에서 또 꺼내야 한다.


#TIL #elixirlang keyword lists도 대괄호 접근 구문을 사용할 수 있다

timeout = opts[:timeout] || 10_000

Programming Phoenix 1.4 책에서 본 소스 코드다. 잠깐 Map이 아니라 Keyword인데 저렇게 쓸 수 있었단 말이야? 몰라서 Keyword.get/3 함수를 사용했다.

iex> keywords = [a: 1, b: 2, a: 3]
[a: 1, b: 2, a: 3]
iex> keywords[:a]
1

keyword lists도 대괄호 접근(square-brackets access) 구문을 사용할 수 있다. key로 찾은 첫 번째 value를 리턴한다.

iex> Keyword.get_values([a: 1, b: 2, a: 3], :a)
[1, 3]

해당 key의 value를 모두 가져오려면 Keyword.get_values/2 함수를 사용한다.


#TIL #elixirlang milliseconds로 바꾸는 함수

iex> :timer.seconds(1)
1000
iex> :timer.minutes(1)
60000
iex> :timer.hours(1)
3600000
iex> :timer.hms(1, 0, 0)
3600000
iex> :timer.hms(1, 1, 1)
3661000

왜 elixir에 없지? 만들어서 쓰다가 erlang에 있는 걸 찾았다.


#TIL #elixirlang 문자열 pattern matching

def join("videos:" <> video_id, _params, socket) do
  {:ok, assign(socket, :video_id, String.to_integer(video_id))}
end

Programming Phoenix 1.4 책애서 본 예제다. 문자열 패턴 매칭도 되는구나. 한 번도 안 써봤다. 책에서 처음 봤다.

iex> "videos:" <> video_id = "videos:1234"
"videos:1234"
iex> video_id
"1234"

이렇게 쓸 수 있다.


#TIL #linux 프로세스가 어떻게 죽었는지 모를 때, 보는 커널의 메시지 버퍼

덤프가 안 남아서 왜 프로세스가 죽었는지 모를 때, 커널의 메시지 버퍼를 출력해서 보면 실마리를 찾을 수 있다.

$ dmesg -T | grep -i -B100 'killed process'

dmesg 명령어로 커널의 메시지 버퍼를 출력한다. -B 옵션을 사용해서 killed process 앞 라인을 같이 출력한다.

[ pid ]   uid  tgid total_vm      rss pgtables_bytes swapents oom_score_adj name
...
[26864]   601 26864  5526755  4012153 34770944        0             0 beam.smp
[27029]   601 27029     2066      282    61440        0             0 inet_gethost
[27030]   601 27030     3654      406    73728        0             0 inet_gethost
[31181]     0 31181     6438     2147   110592        0             0 atop
[32471]     0 32471    26996      833   253952        0             0 sshd
[32578]   601 32578    26996      450   249856        0             0 sshd
[32579]   601 32579     5813      924    94208        0             0 bash
Out of memory: Kill process 26864 (beam.smp) score 979 or sacrifice child
Killed process 26864 (beam.smp) total-vm:..kB, anon-rss:...kB, file-rss:...kB, shmem-rss:...kB

아~ out of memory 에러로 죽었구나

참고 - What killed my process and why? - stackoverflow.com


#TIL #bash 문자열 부분 삭제

문자열 부분 삭제로 원하는 정보를 간단하게 추출할 수 있다.

$ value="version=1.0.1"
$ echo ${value##*=}
1.0.1
$ echo ${value%%=*}
version

추출이 아니라 삭제다. ##*= 연산자는 앞부분에서 *= 패턴에 가장 길게 일치하는 문자열을 삭제한다. 즉, 가장 길게 일치하는 version= 문자열을 삭제한다. %%=* 연산자는 반대다. 뒷부분에서 삭제한다. =1.0.1 문자열을 삭제한다.

참고


#TIL #jenkins choice 파라미터를 중복해서 정의하고 있다면 Extensible Choice Parameter 플러그인

/ddiary/assets/2019-12-04-til-jenkins-extensible-choice-parameter-00.jpg

jenkins 프로젝트를 빌드할 때, 파라미터를 받게 할 수 있다. branch 이름을 파라미터로 추가할 때, text 파라미터가 아닌 choice 파라미터를 선호한다. 사용하는 branch 이름이 뻔할 때, 타이핑 실수가 없기 때문이다.

타이핑 실수도 없고 좋다. 문제는 choice 파라미터의 항목 업데이트다. 만약 여러 프로젝트에서 똑같은 choice 파라미터를 사용하면 하나하나 다 고쳐줘야 한다.

하나씩 업데이트하다가 분명히 나만 괴로운 게 아닐 거란 생각이 들었다. Extensible Choice Parameter 플러그인을 찾았다. 나보다 더 적극적인 프로그래머가 문제를 해결해놨다. 전역 choice 파라미터를 정의하고 필요한 프로젝트에서 쓸 수 있다. 복사가 아니라 참조 개념이다. 전역 choice 파라미터 항목을 변경하면 사용하는 프로젝트에 반영된다.

Jenkins 관리 > 환경 설정 > Extensible Choice: Available Choice Providers

Global Choice Parameter 항목을 선택하고 프로젝트 공통으로 사용할 choice 항목을 정의한다.

프로젝트 선택 > 구성 > 이 빌드는 매개변수가 있습니다 > 매개변수 추가

Extensible Choice 매개변수를 추가하고 Choice Provider로 Global Choice Parameter를 선택한다.


#TIL #gitlab CI/CD 파이프라인 시작 조건을 rules로 제어

gitlab CI/CD 파이프라인 rules 설정 파라미터를 사용하면 특정 조건에만 파이프라인을 시작할 수 있다.

rules:
  - if: '$CI_COMMIT_REF_NAME == "master" || $CI_COMMIT_REF_NAME =~ /^deploy_.*$/'
    changes:
      - src/*
      - src/**/*
      - data_table/*
      - data_table/**/*
    when: always
  - if: '$CI_PIPELINE_SOURCE == "web"'
    when: manual

master 브랜치와 deploy_ 문자열로 시작하는 브랜치에 머지하고 특정 디렉터리 파일이 변경됐을 때, 실행한다. 웹에서 버튼을 눌러 시작했을 때, 동작을 다르게 할 수 있다. 웹에서 버튼을 누르면 조건 검사를 하지 않고 무조건 파이프라인을 시작한다.

12.3.5 버전 기준으로 only/except 설정 파라미터도 있지만 언제 변경될 지 모른다는 경고가 있다. 복잡하지만 사라질 위험이 적은 rules를 사용했다.

trigger:
  script:
    - 'TOKEN="PRIVATE-TOKEN: XXXXXXXXXXXXXXXXXXXXXXXX"'
    - export LAST_COMMIT=$(curl --header "$TOKEN" "$API_URL" | python -c "import sys, json; print json.load(sys.stdin)[1]['sha']")
    - echo "" >trigger_variables
    - if git diff $LAST_COMMIT HEAD --name-only|grep "src/|data_table/"; then
    -     echo 'export TRIGGER="true"' >>trigger_variables
    - fi
    stage: trigger
    artifacts:
      paths:
        - trigger_variables

rules 설정 파라미터가 나오기 전에는 첫 파이프라인 스테이지에서 변경 사항을 직접 조사했다. 파이프라인 스테이지 간 정보를 주고받는 방법이 없어서 파일로 만들어서 artifacts로 다음 스테이지에 전달한다.

예전 방식도 그럭저럭 동작은 했다. 파이프라인 시작 후 검사해서 시작은 했지만, 조건에 안 맞아 실행은 안 한 파이프라이이 많다. 파이프라인이 연속으로 여러 개 생기면 gitlab이 파이프라인을 취소시키는 최적화를 한다. 특정 디렉터리 변경 사항을 관찰하고 실행하는 설정을 알 리 없는 gitlab이 중요한 파이프라인을 취소시키곤 했다.