-->

이미지 너무 크다

[SVN] Window Server에서 Repository 생성

 

 

아파치 서브버전은 Git과 함께 형상관리의 중요한 툴로 자리 잡고 있다. 보통 레거시 프로젝트들은 svn을 사용한다. 

리눅스에 svn 서버를 설치하기도 하는데 회사엔 window server에 설치되어있어 작업해보도록 하겠다.

cmd로도 작업할 수 있지만 윈도우의 강력한 cli인 powershell을 하나 켜주자.

 

1. svn 설치 및 확인

PS D:\> svn help
사용법: svn <subcommand> [options] [args]
Subversion 명령행 클라이언트 버전 1.6.0.
'svn help <subcommand>'를 사용하여 특정 명령에 대하여 도움말을 얻으십시오.
'svn --version'를 사용하여 버전과 원격접속 모듈에 대한 정보를 얻으십시오.
 또는 'svn --version --quiet'를 사용하여 버전 정보만 얻으십시오.

대부분의 하위 명령들은 재귀적으로 수행하면서 파일이나 디렉토리를 인자로 취합니다.
명령들에 인자가 주어지지 않으면 현재 디렉토리를 포함하여 재귀적으로 수행하게
됩니다.

가능한 명령:
   add
   blame (praise, annotate, ann)
   cat
   changelist (cl)
   checkout (co)
   cleanup
   commit (ci)
   copy (cp)
   delete (del, remove, rm)
   diff (di)
   export
   help (?, h)
   import
   info
   list (ls)
   lock
   log
   merge
   mergeinfo
   mkdir
   move (mv, rename, ren)
   propdel (pdel, pd)
   propedit (pedit, pe)
   propget (pget, pg)
   proplist (plist, pl)
   propset (pset, ps)
   resolve
   resolved
   revert
   status (stat, st)
   switch (sw)
   unlock
   update (up)

Subversion은 형상관리를 위한 도구입니다.
더 상세한 정보를 위해서는 http://subversion.tigris.org/ 를 방문하세요.
PS D:\>

svn 이 설치되어 있어야한다. 

 

2. svnadmin create 

PS D:\> svnadmin help
일반적인 사용법: svnadmin SUBCOMMAND REPOS_PATH  [ARGS & OPTIONS ...]
특정 부속 명령의 사용법을 위해서는 'svnadmin help <subcommand>' 를 참조하십시오.
'svnlook --version'으로 버전과 파일시스템 모듈을 볼 수가 있습니다.

가능한 하위 명령 목록:
   crashtest
   create
   deltify
   dump
   help (?, h)
   hotcopy
   list-dblogs
   list-unused-dblogs
   load
   lslocks
   lstxns
   pack
   recover
   rmlocks
   rmtxns
   setlog
   setrevprop
   setuuid
   upgrade
   verify

svnadmin 명령어를 통해 repository를 새로 생성할 수 있다.

 

PS D:\> cd D:\Repository\
PS D:\Repository> svnadmin create --fs-type fsfs android

원하는 경로로 이동해 파일시스템 형식의 repository를 생성해준다.

svn repository가 정상적으로 생성되었다.

 

3. 환경설정

conf 폴더로 이동해 svnserve 파일을 열어주자.

#svnserve

### This file controls the configuration of the svnserve daemon, if you
### use it to allow access to this repository.  (If you only allow
### access through http: and/or file: URLs, then this file is
### irrelevant.)

### Visit http://subversion.tigris.org/ for more information.

[general]
### These options control access to the repository for unauthenticated
### and authenticated users.  Valid values are "write", "read",
### and "none".  The sample settings below are the defaults.
#anon-access = read
# auth-access = write
### The password-db option controls the location of the password
### database file.  Unless you specify a path starting with a /,
### the file's location is relative to the directory containing
### this configuration file.
### If SASL is enabled (see below), this file will NOT be used.
### Uncomment the line below to use the default password file.
# password-db = passwd
### The authz-db option controls the location of the authorization
### rules for path-based access control.  Unless you specify a path
### starting with a /, the file's location is relative to the the
### directory containing this file.  If you don't specify an
### authz-db, no path-based access control is done.
### Uncomment the line below to use the default authorization file.
# authz-db = authz
### This option specifies the authentication realm of the repository.
### If two repositories have the same authentication realm, they should
### have the same password database, and vice versa.  The default realm
### is repository's uuid.
# realm = My First Repository

[sasl]
### This option specifies whether you want to use the Cyrus SASL
### library for authentication. Default is false.
### This section will be ignored if svnserve is not built with Cyrus
### SASL support; to check, run 'svnserve --version' and look for a line
### reading 'Cyrus SASL authentication is available.'
# use-sasl = true
### These options specify the desired strength of the security layer
### that you want SASL to provide. 0 means no encryption, 1 means
### integrity-checking only, values larger than 1 are correlated
### to the effective key length for encryption (e.g. 128 means 128-bit
### encryption). The values below are the defaults.
# min-encryption = 0
# max-encryption = 256

 

원하는 환경설정을 해주면 되는데 아무나 접근할 수 없게 접근 조치만 변경한다.

...
[general]
### These options control access to the repository for unauthenticated
### and authenticated users.  Valid values are "write", "read",
### and "none".  The sample settings below are the defaults.
anon-access = none
auth-access = write
### The password-db option controls the location of the password
### database file.  Unless you specify a path starting with a /,
### the file's location is relative to the directory containing
### this configuration file.
### If SASL is enabled (see below), this file will NOT be used.
### Uncomment the line below to use the default password file.
password-db = passwd
### The authz-db option controls the location of the authorization
...

외부 접근을 금지하고 인가된 계정은 write권한을 주었다.

 

4. 계정생성

conf 폴더의 passwd 파일을 열어서 계정을 추가한다.

#passwd

### This file is an example password file for svnserve.
### Its format is similar to that of svnserve.conf. As shown in the
### example below it contains one section labelled [users].
### The name and password for each user follow, one account per line.

[users]
# harry = harryssecret
# sally = sallyssecret
minggu = minggu123

id = password 형식으로 만들어주자.

 

5. 연결 확인

안드로이드 스튜디오에 Version Control을 Subversion으로 추가하고 

아까 설정한 아이디와 패스워드를 입력한다.

정상적으로 연결되었다.

 

 

[Jenkins] Wildfly 배포 자동화

 

 

<실패 후>

Dood라는 개념이 있다. Docker Out Of Docker라는 개념인데 jenkins를 docker container로 띄우면 해당 container안에서 docker를 설치해 작업하는 Docker In Docker 방식이 있고 Docker Out Of Docker 방식이 있는데 DIND는 docker에서도 지양해야 하는 방법이라 Dood로 진행하려고 한다.

이를 위해 jenkins를 build할 때 host os의 docker.sock 소켓 파일을 jenkins container 내부와 volume 설정으로 연결해야 한다.

저번에 jenkins를 설치하면서 sock 연결은 했지만 jenkins container 내부에도 여러 파일들을 install 해야 한다.

따라서 Dockerfile을 다음과 같이 수정하여 작업하자.

 

이전 Dockerfile

FROM jenkins/jenkins:centos7
USER root

 

수정

FROM jenkins/jenkins:centos7

#metadata
LABEL name="minggu"
LABEL email="minha1002@gmail.com"
LABEL description="jenkins test"

USER root

# Install docker
RUN yum update -y && yum install -y docker wget

# Add deploy.sh
ADD /opt/docker/dockerfiles/jenkins/resouces/wildfly_deploy.sh ~/wildfly_deploy.sh

 

확인

$ docker exec jenkinsC docker ps -a
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                                                                                      NAMES
e68645cf1be6        docker_wildfly      "/bin/sh -c '/opt/..."   3 hours ago         Up 2 hours          0.0.0.0:1003->8080/tcp, :::1003->8080/tcp, 0.0.0.0:1004->9990/tcp, :::1004->9990/tcp       wildflyC
f1b4e6feb68b        docker_jenkins      "/sbin/tini -- /us..."   26 hours ago        Up 2 hours          0.0.0.0:50000->50000/tcp, :::50000->50000/tcp, 0.0.0.0:5080->8080/tcp, :::5080->8080/tcp   jenkinsC

이로써 jenkins container 안에서 wildfly container로 접근이 가능해졌다!

 

 

 

 

 

저번 시간에 Jenkins 안에 SVN repository와 연결된 item을 생성했다.

해당 프로젝트를 svn commit 이 이루어지면 자동으로 build 하여 배포할 수 있도록 자동화를 할 것이다.

item이 생성됐고 SVN이 연결되었다고 가정하고 진행해보자.

https://minggu92.tistory.com/85

 

[Jenkins] Jenkins - SVN 연동

[Jenkins] Jenkins - SVN 연동 보통 Git을 많이 연동하는데 사내 인프라에서는 SVN을 사용하므로 SVN 연동을 먼저 작업해보겠다. 1. SVN plugin 설치 'Jenkins 관리' -> '플러그인 관리' -> '설치 가능' 탭 ->..

minggu92.tistory.com

 

 

1. Plugin 설치

Deploy to container를 설치해주자. Subversion과 Slack Notification도 설치 안 했다면 고고

 

 

2. JDK 추가

Jenkins 관리 - Global Tool Configuration

 

에서 Add JDK을 클릭한다.

 

Install automatically 체크 버튼을 해제하여 JAVA_HOME을 수동으로 설정해 줄 수 있다. 

근데 일단 자동으로 한번 생성!

 

아 생각해보니 jenkins image에 이미 jdk가 포함되어 있었다.

$ docker inspect jenkinsC | grep 'JAVA*'
                "JAVA_HOME=/opt/java/openjdk"

ㅋㅎ 해당 경로로 잡아주자

 

 

 

3. MAVEN 설정

jdk를 추가하고 아래로 쭉 내려보면

이렇게 설정해 줄 수 있는데 pom.xml에 들어가 보면 메이븐 버전 확인을 할 수 있다.

마찬가지로 HOME PATH를 설정할 수 있으니 편한 대로 진행해보자.

 

4. 빌드 설정

 

 

자동화하려는 item의 구성으로 들어가면 build 관련 설정을 할 수 있다.

 

4.1) 빌드 유발

Build Trigger는 매 3분마다 Push 된 내역이 있으면 자동으로 빌드 진행할 수 있도록 스케줄링 해주자.

 

4.2) Build

Invoke top-level Maven targets을 선택해 아까 설정한 Maven version을 입력해준다.

Goalsclean package를 입력해준다.  -DskipTests=true는 테스트를 스킵하겠다는 명령어이다.

maven clean package는 maven clean install과 비슷한 개념이다. 

 

다음으로 Add build step 버튼을 클릭해 Excute Shell을 추가해준다.

해당 설정에서 Wildfly에 Deploiy 하기 위한 쉘을 미리 Dockerfile로 ADD해 줬다.

#deploy wildfly war by minggu 20220407

JENKINS_HOME=/var/jenkins_home
WILDFLY_HOME=/opt/jboss/wildfly
DATE=`date +%Y%m%d`
APPLICATION_NAME=minggu-test
WAR_FILE=`find $JENKINS_HOME/workspace/$APPLICATION_NAME/target -name '*.war' -exec basename {} \;`

# Move war to backup folder
docker exec wildflyC mkdir -p $WILDFLY_HOME/deployments_backup/$DATE \
			&& mv $WILDFLY_HOME/standalone/deployments/$WAR_FILE $WILDFLY_HOME/deployments_backup/$DATE

# Copy war file to wildfly container
docker cp $JENKINS_HOME/workspace/$APPLICATION_NAME/target/$WAR_FILE \
                        wildflyC:$WILDFLY_HOME/standalone/deployments/

# Restart wildfly Web Server container
docker exec wildflyC bash $WILDFLY_HOME/bin/jboss-cli.sh --connect :reload

후.. 쉘짜는거 어렵다 나중에 최적화해야지

 

 

 

4.3) 빌드 후 조치

빌드가 되면 war파일을 컨테이너로 배포할 수 있도록 세팅해보자.

 

테스트로 돌렸을 때 war가 생성되는 경로를 파악해두긴 했다.

 

WAR/EAR files는 war파일 경로를, Context path는 "/" 혹은 root경로를 입력하면 된다.

 

Add Container를 눌러 wildfly admin 계정 정보와 URL을 입력하도록 하자.

 

 

 

5. Test

그리고 빌드를 해보니

 

후 일단 실패...

 

build단계에서 쉘로 직접 docker에 배포를 했다.

....
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  02:17 min
[INFO] Finished at: 2022-04-07T18:08:14+09:00
[INFO] ------------------------------------------------------------------------
[minggu-test] $ /bin/sh -xe /tmp/jenkins15976245722877408796.sh
+ /root/wildfly_deploy.sh
{
    "outcome" => "success",
    "result" => undefined
}
[Slack Notifications] found #32 as previous completed, non-aborted build
[Slack Notifications] will send OnBackToNormalNotification because build matches and user preferences allow it
Finished: SUCCESS

ㅠㅠㅠ

 

 

 

<참고>

https://waspro.tistory.com/552

 

[Jenkins] Jenkins를 활용한 WildFly CI/CD 환경 구성

본 포스팅에서는 Jenkins를 활용한 WildFly CI/CD 환경 구성 방법에 대해 살펴보겠습니다. Jenkins 설치 과정은 아래 포스팅을 참고하세요. [Jenkins] Jenkins 설치 가이드 전형적인 Legacy 환경의 Web Ap..

waspro.tistory.com

https://kimeck.tistory.com/31

 

[Docker] Jenkins&Tomcat 자동화 배포(SVN) -3

 | 젠킨스란? 젠킨스는 소프트웨어 개발 시 지속적으로 통합 서비스를 제공하는 툴이다. CI(Continuous Integration) 툴 이라고 표현한다. 다수의 개발자들이 하나의 프로그램을 개발할 때 버전 충돌을

kimeck.tistory.com

https://examples.javacodegeeks.com/enterprise-java/jboss-wildfly/wildfly-maven-plugin-example/

 

Wildfly Maven Plugin Example

This is an article about using the Wildfly Maven Plugin, a plugin developed by Red Hat that allows you to deploy, undeploy, and manage applications on a

examples.javacodegeeks.com

 

[Jenkins] Slack으로 Jenkins 알림받기

 

jenkins로 multi pipeline을 구축하게 되면 여기저기서 쏟아져 나오는 빌드 과정에 대한 로그의 관리 필요성을 알게 된다.

jenkins를 연동하면 단순 로그 뿐 아니라 다양한 알림을 console에 접속하지 않고 slack으로 바로바로 확인할 수 있게 되니 내부망에만 구축된 jenkins접근에 대한 불편함도 해소할 수 있을 것이다.

 

1. Slack 계정생성 및 새 채널 생성

https://slack.com/intl/ko-kr/get-started#/createnew

 

로그인

Slack에 로그인하거나 팀과 함께 무료로 체험해보세요. 시작하려면 이메일 주소를 입력하세요.

slack.com

slack 회원가입이야 뚝딱하니까 스킵하고 채널부터 새로 생성해준다.

 

2. jenkins 앱 추가

'Slack 찾아보기'를 누르고 '앱'을 클릭한다.

 

jenkins를 검색하고 클릭

 

바로 추가 클릭

 

방금 생성한 채널을 선택하고

 

다음과 같이 가이드라인이 나오는데 페이지 최하단 혹은 3단계를 보면

 

서브도메인과 토큰값이 나온다. 

자 이제 jenkins 환경설정을 해보자.

 

 

3. Jenkins에 slack notification plugin 설치

 

검색 후 'install without restart'를 클릭해 바로 설치하자.

뚝딱

 

4. jenkins 시스템 설정

시스템 설정 최하단에 Slack 환경설정이 있다.

여기서 Workspace는 도메인명을 넣고 Credential을 Secret Text로 추가해 아까 얻은 토큰 값을 적어주자.

 

그리고 Test Connection 을 클릭해주면

Success!

 

5. jenkins project setting

알림을 받을 프로젝트의 '구성'에 들어간다.

 

'빌드 후 조치'에 Slack Notifications가 생겼다.

 

체크 다 때려박고 저장

 

6. Build Test

jenkins console에서 빌드 시작!

 

 

성공!

[Jenkins] Jenkins - SVN 연동

 

보통 Git을 많이 연동하는데 사내 인프라에서는 SVN을 사용하므로 SVN 연동을 먼저 작업해보겠다.

 

1. SVN plugin 설치

 

'Jenkins 관리' -> '플러그인 관리' -> '설치 가능' 탭 -> subversion 검색 -> 재시작

 

2. 새로운 Item

'새로운 Item'을 클릭해준다.

 

3. Freestyle project

 

4.  SVN 정보 입력

 

5. Credentials 추가

'Username with password' 를 선택하고 username에 svn 서버의 계정정보를 입력해주면 된다.

 

6. Project Build

 

'지금 빌드'를 클릭!

 

7. 빌드 후 작업공간 확인

성공적으로 SVN 연동이 되었다.

[Jenkins] Docker로 Jenkins 빌드 및 설치

 

계속 미뤄왔던 젠킨스 도입. 

지속적인 빌드와 배포 자동화를 의미하는 CI/CD 쪽에서 jenkins는 대다수 개발자들이 이용하는 툴이다.

매번 war 수동으로 말아서 서버로 밀어 넣고 restart 하는 삶은 이제 그만,, 

한번 공부하고 써보자! 무중단배포하는 그날까지~~~~~~~

 

 

1. 공식 이미지 존재 여부 확인

docker hub에 Jenkins 공식 이미지가 존재한다. 

https://github.com/jenkinsci/docker/blob/master/README.md

 

GitHub - jenkinsci/docker: Docker official jenkins repo

Docker official jenkins repo. Contribute to jenkinsci/docker development by creating an account on GitHub.

github.com

 

 

 

2. Volume 폴더 생성

jenkins 폴더에 접근할 수 있도록 volume 폴더를 만들고 권한을 주자

$ mkdir -p /opt/docker/volume/jenkins && chmod -R 777 "$_"

 

3. Dockerfile 생성

FROM jenkins/jenkins:centos7
USER root

일단 dockerfile쪽은 작성할 게 별로 없는데 나중에 추가하도록 하자

 

추가.

FROM jenkins/jenkins:centos7

#metadata
LABEL name="minggu"
LABEL email="minha1002@gmail.com"
LABEL description="jenkins test"

USER root

# Install docker
RUN yum update -y && yum install -y docker wget

 

4. docker-compose.yml

# docker-compose.yml

version: "3.8"

services: 
  wildfly:
	...
    
  jenkins:
    container_name: jenkinsC
    enviroment:
    	TZ: "Asia/Seoul"
    build:
      context: ./dockerfiles/jenkins/
      dockerfile: jenkins.Dockerfile    
    ports:
      - 5080:8080
      - 50000:50000
    volumes:
      - /opt/docker/volume/jenkins:/var/jenkins_home
      - /var/run/docker.sock:/var/run/docker.sock

저번에 만들어 둔 wildfly 밑에 새로 jenkins라는 이름의 service를 만들었다. 간단간단 

 

5. docker compose up을 통한 build

#특정 service 명 입력
$ docker compose up jenkins
[+] Building 4.0s (6/6) FINISHED
 => [internal] load build definition from jenkins.Dockerfile                                                                                                                     0.4s
 => => transferring dockerfile: 144B                                                                                                                                             0.0s
 => [internal] load .dockerignore                                                                                                                                                0.6s
 => => transferring context: 2B                                                                                                                                                  0.0s
 => [internal] load metadata for docker.io/jenkins/jenkins:centos7                                                                                                               2.2s
 => [auth] jenkins/jenkins:pull token for registry-1.docker.io                                                                                                                   0.0s
 => CACHED [1/1] FROM docker.io/jenkins/jenkins:centos7@sha256:84290087666555a858d5540a46875e59a0ac1ea9e18683b51972d8bf27f05e78                                                  0.2s
 => => resolve docker.io/jenkins/jenkins:centos7@sha256:84290087666555a858d5540a46875e59a0ac1ea9e18683b51972d8bf27f05e78                                                         0.2s
 => exporting to image                                                                                                                                                           0.8s
 => => exporting layers                                                                                                                                                          0.0s
 => => writing image sha256:72aeccf15a60e47a9bfec4986b18762bd255f5da9bec60562579acc5349c3f53                                                                                     0.0s
 => => naming to docker.io/library/docker_jenkins                                                                                                                                0.1s

Use 'docker scan' to run Snyk tests against images to find vulnerabilities and learn how to fix them
[+] Running 1/1
 ⠿ Container jenkinsC  Created                                                                                                                                                   0.5s
Attaching to jenkinsC
jenkinsC  | Running from: /usr/share/jenkins/jenkins.war
jenkinsC  | webroot: EnvVars.masterEnvVars.get("JENKINS_HOME")
jenkinsC  | 2022-04-04 06:18:58.532+0000 [id=1] INFO    org.eclipse.jetty.util.log.Log#initialized: Logging initialized @1443ms to org.eclipse.jetty.util.log.JavaUtilLog
jenkinsC  | 2022-04-04 06:18:58.783+0000 [id=1] INFO    winstone.Logger#logInternal: Beginning extraction from war file
jenkinsC  | 2022-04-04 06:19:02.344+0000 [id=1] WARNING o.e.j.s.handler.ContextHandler#setContextPath: Empty contextPath
jenkinsC  | 2022-04-04 06:19:02.500+0000 [id=1] INFO    org.eclipse.jetty.server.Server#doStart: jetty-9.4.45.v20220203; built: 2022-02-03T09:14:34.105Z; git: 4a0c91c0be53805e3fcffdcdcc9587d5301863db; jvm 11.0.14.1+1
jenkinsC  | 2022-04-04 06:19:03.180+0000 [id=1] INFO    o.e.j.w.StandardDescriptorProcessor#visitServlet: NO JSP Support for /, did not find org.eclipse.jetty.jsp.JettyJspServlet
jenkinsC  | 2022-04-04 06:19:03.276+0000 [id=1] INFO    o.e.j.s.s.DefaultSessionIdManager#doStart: DefaultSessionIdManager workerName=node0
jenkinsC  | 2022-04-04 06:19:03.277+0000 [id=1] INFO    o.e.j.s.s.DefaultSessionIdManager#doStart: No SessionScavenger set, using defaults
jenkinsC  | 2022-04-04 06:19:03.279+0000 [id=1] INFO    o.e.j.server.session.HouseKeeper#startScavenging: node0 Scavenging every 660000ms
jenkinsC  | 2022-04-04 06:19:04.470+0000 [id=1] INFO    hudson.WebAppMain#contextInitialized: Jenkins home directory: /var/jenkins_home found at: EnvVars.masterEnvVars.get("JENKINS_HOME")
jenkinsC  | 2022-04-04 06:19:05.530+0000 [id=1] INFO    o.e.j.s.handler.ContextHandler#doStart: Started w.@6fca5907{Jenkins v2.341,/,file:///var/jenkins_home/war/,AVAILABLE}{/var/jenkins_home/war}
jenkinsC  | 2022-04-04 06:19:05.591+0000 [id=1] INFO    o.e.j.server.AbstractConnector#doStart: Started ServerConnector@2002348{HTTP/1.1, (http/1.1)}{0.0.0.0:8080}
jenkinsC  | 2022-04-04 06:19:05.593+0000 [id=1] INFO    org.eclipse.jetty.server.Server#doStart: Started @8508ms
jenkinsC  | 2022-04-04 06:19:05.596+0000 [id=24]        INFO    winstone.Logger#logInternal: Winstone Servlet Engine running: controlPort=disabled
jenkinsC  | 2022-04-04 06:19:06.257+0000 [id=29]        INFO    hudson.PluginManager#loadDetachedPlugins: Upgrading Jenkins. The last running version was 2.332.1. This Jenkins is version 2.341.
jenkinsC  | 2022-04-04 06:19:06.361+0000 [id=31]        INFO    jenkins.InitReactorRunner$1#onAttained: Started initialization
jenkinsC  | 2022-04-04 06:19:06.371+0000 [id=29]        INFO    hudson.PluginManager#loadDetachedPlugins: Upgraded Jenkins from version 2.332.1 to version 2.341. Loaded detached plugins (and dependencies): []
jenkinsC  | 2022-04-04 06:19:06.391+0000 [id=29]        INFO    jenkins.InitReactorRunner$1#onAttained: Listed all plugins
jenkinsC  | 2022-04-04 06:19:08.524+0000 [id=36]        INFO    jenkins.InitReactorRunner$1#onAttained: Prepared all plugins
jenkinsC  | 2022-04-04 06:19:08.535+0000 [id=36]        INFO    jenkins.InitReactorRunner$1#onAttained: Started all plugins
jenkinsC  | 2022-04-04 06:19:08.575+0000 [id=36]        INFO    jenkins.InitReactorRunner$1#onAttained: Augmented all extensions
jenkinsC  | 2022-04-04 06:19:09.174+0000 [id=37]        INFO    jenkins.InitReactorRunner$1#onAttained: System config loaded
jenkinsC  | 2022-04-04 06:19:09.175+0000 [id=37]        INFO    jenkins.InitReactorRunner$1#onAttained: System config adapted
jenkinsC  | 2022-04-04 06:19:09.176+0000 [id=37]        INFO    jenkins.InitReactorRunner$1#onAttained: Loaded all jobs
jenkinsC  | 2022-04-04 06:19:09.180+0000 [id=37]        INFO    jenkins.InitReactorRunner$1#onAttained: Configuration for all jobs updated
jenkinsC  | 2022-04-04 06:19:09.217+0000 [id=51]        INFO    hudson.model.AsyncPeriodicWork#lambda$doRun$1: Started Download metadata
jenkinsC  | 2022-04-04 06:19:09.236+0000 [id=51]        INFO    hudson.model.AsyncPeriodicWork#lambda$doRun$1: Finished Download metadata. 13 ms
jenkinsC  | 2022-04-04 06:19:09.447+0000 [id=38]        INFO    jenkins.install.SetupWizard#init:
jenkinsC  |
jenkinsC  | *************************************************************
jenkinsC  | *************************************************************
jenkinsC  | *************************************************************
jenkinsC  |
jenkinsC  | Jenkins initial setup is required. An admin user has been created and a password generated.
jenkinsC  | Please use the following password to proceed to installation:
jenkinsC  |
jenkinsC  | 38e626cc44dd4db89484ba29c29e8da6
jenkinsC  |
jenkinsC  | This may also be found at: /var/jenkins_home/secrets/initialAdminPassword
jenkinsC  |
jenkinsC  | *************************************************************
jenkinsC  | *************************************************************
jenkinsC  | *************************************************************
jenkinsC  |

금방 설치되더니 하단에 generate 된 password 가 보인다.

혹시라도 background로 docker를 실행했다면 docker logs [docker name]으로 로그를 다시 찍어보면 된다.

 

6. jenkins console 접속

아까 열어둔 서버:5080 포트로 접속해보면

짜잔 접속이 된다. 

방금 전 패스워드를 입력하고

플러그인을 설치해주자.

 

admin 계정을 생성해주고 고정 URL을 정해주면

 

 

 

 

<참고>

https://github.com/jenkinsci/docker/blob/master/README.md

 

GitHub - jenkinsci/docker: Docker official jenkins repo

Docker official jenkins repo. Contribute to jenkinsci/docker development by creating an account on GitHub.

github.com

 

[Kotlin] Firebase 연동 & Realtime Database 조작

 

너무 Java만 쓰는 것 같아서 오랜만에 Kotlin으로 작업..!

사실 글 하나를 올리더라도 Java와 Kotlin 두 개의 코드를 올릴 수 있겠지만..

요새 이것저것 많이 하고 있는 와중에 포스팅에 생각보다 많은 시간이 들어서 쉽지가 않다...

우는 소리 말고 부지런히 해보자! 다 성장을 위한 거니까

 

Realtime Database는 Google Firebase에서 제공하는 database의 한 종류이다.

Firestore의 이전 버전이라고 할 수 있다. 둘을 비교해놓은 글들을 읽어보면 대충 Firesotre가 최신 버전이고 price policy가 다르다 정도로 압축되는 것 같은데 구체적으로 어떤 기능이 다르고, 가격정책이 어떻게 다른지는 소개하지 않을 것이다.

Realtime Database가 구버전이라고 해도 충분히 활용할 수 있기 때문에 연동하는 샘플을 만들어보자. Kotlin으로 작성도 해보고!

https://minggu92.tistory.com/75

 

[Android] firebase 연동 (최신버전)

[Android] firebase 연동 오랜만에 돌아온 안드로이드 시간. 원래는 블로그를 내가 자주 잊을 수 있는 안드로이드 위주로만 올리려고 하다가.. 웹 개발 쪽 이것저것 올리다 보니 소홀해졌었다. 따라서

minggu92.tistory.com

https://minggu92.tistory.com/80

 

[Android / Java] Cloud Firestore 연동을 통한 Data 조작(feat. RecyclerView)

[Android / Java] Cloud Firestore 연동을 통한 Data 조작(feat. RecyclerView) Cloud Firestore는 Firebase 및 Google Cloud의 모바일, 웹, 서버 개발에 사용되는 유연하고 확장 가능한 데이터베이스입..

minggu92.tistory.com

java로 작성된 이전 글을 참조해보는 것도 좋겠다.

 

 

1. Firebase & Realtime Database 연동

1.1) Firebase 새 프로젝트 생성

 

1.2) Android project 생성

build.gradle(Project)

// Top-level build file where you can add configuration options common to all sub-projects/modules.
plugins {
    id 'com.android.application' version '7.1.2' apply false
    id 'com.android.library' version '7.1.2' apply false
    id 'org.jetbrains.kotlin.android' version '1.6.10' apply false
    //구글서비스 추가
    id 'com.google.gms.google-services' version '4.3.10' apply false
}

task clean(type: Delete) {
    delete rootProject.buildDir
}

 

build.gradle(App)

plugins {
    id 'com.android.application'
    id 'org.jetbrains.kotlin.android'
    id 'com.google.gms.google-services' //구글서비스 추가
}

android {
    compileSdk 32

    defaultConfig {
        applicationId "com.example.realtime_sample"
        minSdk 21
        targetSdk 32
        versionCode 1
        versionName "1.0"

        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
    kotlinOptions {
        jvmTarget = '1.8'
    }

    //뷰 바인딩추가
    buildFeatures {
        viewBinding true
    }
}

dependencies {

    implementation 'androidx.core:core-ktx:1.7.0'
    implementation 'androidx.appcompat:appcompat:1.4.1'
    implementation 'com.google.android.material:material:1.5.0'
    implementation 'androidx.constraintlayout:constraintlayout:2.1.3'
    testImplementation 'junit:junit:4.13.2'
    androidTestImplementation 'androidx.test.ext:junit:1.1.3'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'

    // Import the Firebase BoM
    implementation platform('com.google.firebase:firebase-bom:29.2.1')

    // Declare the dependencies for any other desired Firebase products
    // For example, declare the dependencies for Firebase Authentication and Cloud Firestore
    implementation 'com.google.firebase:firebase-auth-ktx'
    implementation 'com.google.firebase:firebase-database-ktx'

    implementation 'com.google.code.gson:gson:2.9.0'
}

GSON 모듈도 import 시켜주자

 

1.3) Android 앱에 Firebase 추가

SHA1 (gradle.signingReport)

 

1.4) 새 데이터베이스 생성

규칙은 일단 true로 설정해주자.

 

2. Layout

저번과 같이 RecyclerVIew를 이용해보자.

 

2.1) activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.appcompat.widget.LinearLayoutCompat xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MainActivity">

    <androidx.appcompat.widget.LinearLayoutCompat
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">

        <Button
            android:id="@+id/btn_write"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_margin="10dp"
            android:layout_weight="1"
            android:text="유저 추가" />

        <Button
            android:id="@+id/btn_reload"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_margin="10dp"
            android:layout_weight="1"
            android:text="새로고침" />

        <Button
            android:id="@+id/btn_delete"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_margin="10dp"
            android:layout_weight="1"
            android:text="유저 삭제" />

    </androidx.appcompat.widget.LinearLayoutCompat>

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/rv_user_list"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</androidx.appcompat.widget.LinearLayoutCompat>

 

2.2) rv_layout.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_margin="4dp"
    app:cardCornerRadius="8dp">

    <androidx.appcompat.widget.LinearLayoutCompat
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">


        <TextView
            android:id="@+id/tv_name"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_margin="10dp"
            android:layout_weight="1"
            android:text="Name" />

        <TextView
            android:id="@+id/tv_address"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_margin="10dp"
            android:layout_weight="1"
            android:text="Address" />

        <TextView
            android:id="@+id/tv_age"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_margin="10dp"
            android:layout_weight="1"
            android:text="Age" />
    </androidx.appcompat.widget.LinearLayoutCompat>

</androidx.cardview.widget.CardView>

 

2.3) string.xml

<resources>
    <string name="app_name">RealtimeDatabaseSample</string>
    <string name="add">추가</string>
    <string name="cancel">취소</string>
</resources>

 

 

 

3. Kotlin 소스 만들기

3.1) User.kt

import com.google.firebase.database.IgnoreExtraProperties

@IgnoreExtraProperties
data class User(val name: String? = null, val address: String? = null, val age: Int? = null) {
    // Null default values create a no-argument default constructor, which is needed
    // for deserialization from a DataSnapshot.
}

 

3.2) UserAdapter.kt

import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView

class UserAdapter(val userList: ArrayList<User>): RecyclerView.Adapter<UserAdapter.ViewHolder>() {
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
        val view = LayoutInflater.from(parent.context).inflate(R.layout.rv_layout, parent, false)
        return ViewHolder(view)
    }

    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        holder.name.text = userList[position].name
        holder.address.text = userList[position].address
        holder.age.text = userList[position].age.toString()
    }

    override fun getItemCount(): Int {
        return userList.size
    }

    class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
        val name: TextView = itemView.findViewById(R.id.tv_name)
        val address: TextView = itemView.findViewById(R.id.tv_address)
        val age: TextView = itemView.findViewById(R.id.tv_age)
    }
}

 

3.3) MainActivity.kt

import android.annotation.SuppressLint
import android.app.AlertDialog
import android.os.Bundle
import android.util.Log
import android.view.View
import android.widget.EditText
import android.widget.LinearLayout
import android.widget.TextView
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.recyclerview.widget.LinearLayoutManager
import com.example.realtimedatabasesample.databinding.ActivityMainBinding
import com.google.firebase.database.*
import com.google.firebase.database.ktx.database
import com.google.firebase.database.ktx.getValue
import com.google.firebase.ktx.Firebase
import com.google.gson.Gson


class MainActivity : AppCompatActivity() {
    companion object {
        private const val TAG = "MainActivity"
    }

    private lateinit var binding : ActivityMainBinding
    private lateinit var database : DatabaseReference
    private var drUser: DatabaseReference? = null

    val db = FirebaseDatabase.getInstance()
    val userList = arrayListOf<User>()
    val adapter = UserAdapter(userList)


    @SuppressLint("NotifyDataSetChanged")
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityMainBinding.inflate(layoutInflater)
        val view = binding.root
        setContentView(view)

        binding.rvUserList.layoutManager = LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false)
        binding.rvUserList.adapter = adapter

        database = Firebase.database.reference //DatabaseReference

        /* 유저 읽기 */
        binding.btnReload.setOnClickListener(View.OnClickListener {
            database.child("users").get().addOnSuccessListener {

                // 1. 데이터 1회 읽어 RecyclerView에 담기
                /*
                userList.clear()
                for(userValue in it.children){
                    //jsonObject -> ModelClass by Gson
                    val user = Gson().fromJson(userValue.value.toString(), User::class.java)
                    userList.add(user)
                }
                adapter.notifyDataSetChanged()
                Toast.makeText(this, "유저 읽기 성공", Toast.LENGTH_SHORT).show()
                Log.i(TAG, "Got value ${it.value}")
                */
                // 2. 데이터 변화 감지하는 ValueEventListener add
                addUserListener(database)

            }.addOnFailureListener {
                Toast.makeText(this, "유저 읽기 실패", Toast.LENGTH_SHORT).show()
                Log.w(TAG, "Error getting data! $it")
            }
        })

        /* 유저 추가 */
        binding.btnWrite.setOnClickListener(View.OnClickListener {
            //동적으로 AlertDialog 생성
            val builder = AlertDialog.Builder(this)
            val tvName = TextView(this)
            val tvAddress = TextView(this)
            val tvAge = TextView(this)
            tvName.text = "Name"
            tvAddress.text = "Address"
            tvAge.text = "Age"

            val etName = EditText(this)
            val etAddress = EditText(this)
            val etAge = EditText(this)
            etAddress.isSingleLine = true

            val layout = LinearLayout(this)
            layout.orientation = LinearLayout.VERTICAL
            layout.setPadding(16, 16, 16, 16)
            layout.addView(tvName)
            layout.addView(etName)
            layout.addView(tvAge)
            layout.addView(etAge)
            layout.addView(tvAddress)
            layout.addView(etAddress)

            builder.setView(layout)
                .setTitle("유저 추가")
                .setPositiveButton(R.string.add) { _, _ ->
                    val user = hashMapOf(
                        "name" to etName.text.toString(),
                        "address" to etAddress.text.toString(),
                        "age" to etAge.text.toString().toLong().toInt()
                    )
                    database.child("users").push().setValue(user)
                        .addOnSuccessListener {
                            Toast.makeText(this, "유저 추가 성공", Toast.LENGTH_SHORT).show()
                        }
                        .addOnFailureListener {
                            Toast.makeText(this, "유저 추가 실패", Toast.LENGTH_SHORT).show()
                            Log.w(TAG, "error! $it")
                        }

//                    binding.btnReload.callOnClick() //새로고침 버튼 클릭
                }
                .setNegativeButton(R.string.cancel) { _, _ -> }
                .show()
        })

        /* 유저 삭제 */
        binding.btnDelete.setOnClickListener(View.OnClickListener {
            database.child("users").removeValue().addOnSuccessListener {
                Toast.makeText(this, "유저 삭제 완료", Toast.LENGTH_SHORT).show()
            }.addOnFailureListener {
                Toast.makeText(this, "유저 삭제 실패", Toast.LENGTH_SHORT).show()
            }
            adapter.notifyDataSetChanged()
        })

    }

    fun basicReadWrite() {
        // [START write_message]
        // Write a message to the database
        val database = Firebase.database
        val myRef = database.getReference("message")

        myRef.setValue("Hello, World!")
        // [END write_message]

        // [START read_message]
        // Read from the database
        myRef.addValueEventListener(object : ValueEventListener {
            override fun onDataChange(dataSnapshot: DataSnapshot) {
                // This method is called once with the initial value and again
                // whenever data at this location is updated.
                val value = dataSnapshot.getValue<String>()
                Log.d(TAG, "Value is: $value")
            }

            override fun onCancelled(error: DatabaseError) {
                // Failed to read value
                Log.w(TAG, "Failed to read value.", error.toException())
            }
        })
        // [END read_message]
    }

    private fun addUserListener(userDataReference: DatabaseReference){
        val userListener = object : ValueEventListener {
            @SuppressLint("NotifyDataSetChanged")
            override fun onDataChange(dataSnapshot: DataSnapshot) {
                if(dataSnapshot.hasChild("users")){
                    userList.clear()

                    for ( userValue in dataSnapshot.child("users").children){
                        val user = Gson().fromJson(userValue.value.toString(), User::class.java)
                        userList.add(user)
                    }

                    adapter.notifyDataSetChanged()
                }
            }

            override fun onCancelled(databaseError: DatabaseError) {
                // Getting Post failed, log a message
                Log.w(TAG, "loadUsers:onCancelled", databaseError.toException())
            }
        }
        userDataReference.addValueEventListener(userListener)
    }

}

 

 

4.Realtime Database 조작

4.1) 새 데이터 생성

Firestore은 NoSQL 기반 Document 형식으로 되어있는데 Realtime Database는 Json형태로 되어있다.

샘플데이터를 먼저 만들어두자.

 

4.2) 데이터 읽기

/* 유저 읽기 */
binding.btnReload.setOnClickListener(View.OnClickListener {
    database.child("users").get().addOnSuccessListener {
        userList.clear()
        for(json in it.children){
            //jsonObject -> ModelClass by Gson
            val user = Gson().fromJson(json.value.toString(), User::class.java)
            userList.add(user)
        }
        adapter.notifyDataSetChanged()
        Toast.makeText(this, "유저 읽기 성공", Toast.LENGTH_SHORT).show()
        Log.i(TAG, "Got value ${it.value}")
    }.addOnFailureListener {
        Toast.makeText(this, "유저 읽기 실패", Toast.LENGTH_SHORT).show()
        Log.w(TAG, "Error getting data! $it")
    }
})

Json 데이터를 다뤄야 하다 보니까 Gson 라이브러리를 import 시켜놨다. 

 

4.3) 데이터 쓰기

/* 유저 추가 */
binding.btnWrite.setOnClickListener(View.OnClickListener {
    //동적으로 AlertDialog 생성
    val builder = AlertDialog.Builder(this)
    val tvName = TextView(this)
    val tvAddress = TextView(this)
    val tvAge = TextView(this)
    tvName.text = "Name"
    tvAddress.text = "Address"
    tvAge.text = "Age"

    val etName = EditText(this)
    val etAddress = EditText(this)
    val etAge = EditText(this)
    etAddress.isSingleLine = true

    val layout = LinearLayout(this)
    layout.orientation = LinearLayout.VERTICAL
    layout.setPadding(16, 16, 16, 16)
    layout.addView(tvName)
    layout.addView(etName)
    layout.addView(tvAge)
    layout.addView(etAge)
    layout.addView(tvAddress)
    layout.addView(etAddress)

    builder.setView(layout)
        .setTitle("유저 추가")
        .setPositiveButton(R.string.add) { _, _ ->
            val user = hashMapOf(
                "name" to etName.text.toString(),
                "address" to etAddress.text.toString(),
                "age" to etAge.text.toString().toLong().toInt()
            )
            database.child("users").push().setValue(user)
                .addOnSuccessListener {
                    Toast.makeText(this, "유저 추가 성공", Toast.LENGTH_SHORT).show()
                }
                .addOnFailureListener {
                    Toast.makeText(this, "유저 추가 실패", Toast.LENGTH_SHORT).show()
                    Log.w(TAG, "error! $it")
                }

            binding.btnReload.callOnClick() //새로고침 버튼 클릭
        }
        .setNegativeButton(R.string.cancel) { _, _ -> }
        .show()
})

데이터를 쓰고 나서 읽어오기 위해 새로고침 버튼을 클릭하는데 Realtime Database에서는 ValueEventListener를 통해 데이터 변화를 실시간으로 감지할 수 있다.

 

4.4) 데이터 변화 감지

ValueEventListener를 통해 데이터의 변화를 실시간 감지할 수 있다.

전체 children도 가능하고 특정 child도 가능하다.

	...
	/* 유저 읽기 */
    binding.btnReload.setOnClickListener(View.OnClickListener {
        database.child("users").get().addOnSuccessListener {

            // 1. 데이터 1회 읽어 RecyclerView에 담기
            /*
            userList.clear()
            for(userValue in it.children){
                //jsonObject -> ModelClass by Gson
                val user = Gson().fromJson(userValue.value.toString(), User::class.java)
                userList.add(user)
            }
            adapter.notifyDataSetChanged()
            Toast.makeText(this, "유저 읽기 성공", Toast.LENGTH_SHORT).show()
            Log.i(TAG, "Got value ${it.value}")
            */
            // 2. 데이터 변화 감지하는 ValueEventListener add
            addUserListener(database)

        }.addOnFailureListener {
            Toast.makeText(this, "유저 읽기 실패", Toast.LENGTH_SHORT).show()
            Log.w(TAG, "Error getting data! $it")
        }
    })
    
    ...
    
    private fun addUserListener(userDataReference: DatabaseReference){
        val userListener = object : ValueEventListener {
            @SuppressLint("NotifyDataSetChanged")
            override fun onDataChange(dataSnapshot: DataSnapshot) {
                if(dataSnapshot.hasChild("users")){
                    userList.clear()

                    for ( userValue in dataSnapshot.child("users").children){
                        val user = Gson().fromJson(userValue.value.toString(), User::class.java)
                        userList.add(user)
                    }

                    adapter.notifyDataSetChanged()
                }
            }

            override fun onCancelled(databaseError: DatabaseError) {
                // Getting Post failed, log a message
                Log.w(TAG, "loadUsers:onCancelled", databaseError.toException())
            }
        }
        userDataReference.addValueEventListener(userListener)
    }

콘솔에서 데이터를 변경하면 앱 내 데이터도 바로 변경된다

 

 

3.5) 데이터 업데이트 및 삭제

/* 유저 삭제 */
binding.btnDelete.setOnClickListener(View.OnClickListener {
    database.child("users").removeValue().addOnSuccessListener {
        Toast.makeText(this, "유저 삭제 완료", Toast.LENGTH_SHORT).show()
    }.addOnFailureListener {
        Toast.makeText(this, "유저 삭제 실패", Toast.LENGTH_SHORT).show()
    }
    adapter.notifyDataSetChanged()
})

 

 

 

해보니까..별거 아니네!!!! (주말 이틀날림)

https://github.com/minha9012/Realtime-database-sample

 

GitHub - minha9012/Realtime-database-sample: Realtime Database CRUD sample by Kotlin

Realtime Database CRUD sample by Kotlin. Contribute to minha9012/Realtime-database-sample development by creating an account on GitHub.

github.com

 

 

 

<참고>

https://firebase.google.com/docs/database/android/read-and-write?authuser=0#kotlin+ktx 

 

Android에서 데이터 읽기 및 쓰기  |  Firebase Documentation

Join Firebase at Google I/O online May 11-12, 2022. Register now 의견 보내기 Android에서 데이터 읽기 및 쓰기 이 문서에서는 Firebase 데이터를 읽고 쓰는 기초적인 방법을 설명합니다. Firebase 데이터는 FirebaseDataba

firebase.google.com

 

 

사실 좀 예전에 취득했다... 벌써 1년 반이나 지났네...

네트워크 관리사 2급을 딴 목적은 일단 '국가공인자격증' 이라는 점에 있다.

비전공자 출신으로써 한창 공학 학위를 위해 학점은행이나 방통대를 알아보던 중에 IT 국가 공인 자격증을 취득하면 학점은행에서 학점으로 인정해준다는 이야기를 듣고 솔깃해서 준비하게 되었다.

 

1급과 2급?

네트워크 관리사는 1급과 2급이 존재하고 1급은 '민간자격증'이다. 게다가 매우 어렵다고 알고 있다...

2급은 난이도는 높지 않고 비전공자라면 알아야 할 네트워크 지식들로 이루어져 있어서 따면 분명 도움은 된다. 아니 사실 금방 잊히겠지만 도움이 될 것이다..

 

비싸...

각각 필기와 실기시험으로 이루어져있다.

1, 2급 필기는 43000원이고 실기는 1급 100000원, 2급 78000원이다. 

학점 사는 게 이렇게나 비싸다..

 

실기?

네트워크 관리시험의 실기란?실제로 라우터로 세팅을 하는데 마치 리눅스 쉘을 쓰는 느낌이다.다만 세팅방법은 꽤 다르기도 하니 꼭 외워가야 할 것이고또 진짜 네트워크도 연결해본다. 

모의고사 예제

랜케이블을 만들어야 한다. 

다만 이것 때문에 랜케이블 키트를 산다거나 하지는 말자.. 솔직히 손재주 조금만 있더라도 유튜브 영상 통해서 충분히 숙지할 수 있다. 

색깔만 잘 외워가면 되기 때문에 가뜩이나 비싼 응시비에 더 지출하진 말자 ㅜ

 

 

 

 

 

그리고 시험 전 혼자 보려고 만든 필기 정리인데 누군가에게 도움이 될.. 까? 아무튼 나는 이런 식으로 외웠다 정도..

필기 정리.txt
0.00MB

 

그리고 이런 것들도 있긴 한데 누군가에게 도움은 되길

네트워크관리사2급20180204(교사용).hwp
0.06MB
네트워크관리사2급20180422(교사용).hwp
0.04MB
네트워크관리사2급20180715(교사용).hwp
0.08MB
네트워크관리사2급20181021(교사용).hwp
0.03MB
네트워크관리사2급20190217(교사용).hwp
0.06MB
네트워크관리사2급20190519(교사용).hwp
0.10MB
네트워크관리사2급20190825(교사용).hwp
0.07MB
네트워크관리사2급20191110(교사용).hwp
0.06MB
네트워크관리사2급20200216(학생용).hwp
0.03MB

 

서울특별시 전자도서관 어디에 강의도 있던 것 같다. 

서울특별시 전자도서관 IT 어쩌고 교안.7z
6.07MB

 

데모하는 것도 있으니 시험 전에 UI에 익숙해지자

용량이 20mb 넘어서 안올라가네

자격증 주관하는데 가보면 줄 것이다!

솔직히 도커는 고래이미지가 더 귀엽지 않나...쩝

[Docker] docker-compose 사용 (Compose V2)

 

Dockerfile로 image를 build 하고 run 하고 wildfly 웹서버까지 띄워봤다.

그러나 실행할 때 마다 긴 명령어를 입력해야 하는 것이 꽤나 번거로운 일이라는 걸 알게 된다.

docker run --privileged -itd -p 1003:8080 --name wildflyC -v /opt/docker/dockervolume/logs:/opt/jboss/wildfly/standalone/configuration/log:z wildfly_img:1.0

Volume까지 설정하면 이렇게 되는데 여기서 더 길어질 수도 있다 ㅜ

자동화와 유지보수를 위해 Dockerfile을 사용하는데 이를 실행하는 명령어가 어려우니 이것도 정의해야 할 필요성이 느껴진다.

이런 상황에서 docker run 할 때 옵션 값들이 많아지거나, 동시에 여러 컨테이너를 조작해야 한다거나, 컨테이너끼리 통신해야 할 경우 사용하는 훌륭한 툴이 있다.

그게 바로 docker-compose 이다.

한번 설치부터 실행까지 해보자.

 

1. docker-compose 설치 

docker compose에 대해 알아보니 구버전과 V2 가 있더라. 설치 방법은 2개다 올리고 나는 V2로 진행해보겠다.

 

1.1) Docker Compose 

설치는 보통 최신 버전을 curl로 가지고 온다. 

따끈따끈한 신상 버전으로 설치해보자.

$ sudo curl -L "https://github.com/docker/compose/releases/download/v2.3.4/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose

docker-compose에 권한을 줘야 한다.

$ sudo chmod +x /usr/local/bin/docker-compose

설치가 되었으면 버전 확인~!

$ docker --version
Docker version 20.10.14, build a224086

 

`docker-compose` 명령어를 실행했을 때, `bash: /usr/bin/docker-compose: 그런 파일이나 디렉터리가 없습니다`

와 같은 오류가 발생하면 아래처럼 심볼릭 링크를 설정해야 에러가 안 난다고 하는데 나는 에러가 안 났다

$ sudo ln -s /usr/local/bin/docker-compose /usr/bin/docker-compose

명령어 수행은 'docker-compose'로 실행한다

 

 

1.2) Docker Compose V2

Docker Desktop과 호환성을 위한 것 같은데... 일단 도전!!

# 변수설정
$ DOCKER_CONFIG=${DOCKER_CONFIG:-$HOME/.docker}

# 폴더 생성
$ mkdir -p $DOCKER_CONFIG/cli-plugins

# 설치
$ curl -SL https://github.com/docker/compose/releases/download/v2.3.4/docker-compose-linux-x86_64 -o $DOCKER_CONFIG/cli-plugins/docker-compose
 
 % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100   664  100   664    0     0   1328      0 --:--:-- --:--:-- --:--:--  1330
100 25.2M  100 25.2M    0     0  4609k      0  0:00:05  0:00:05 --:--:-- 6451k

# 권한
$ chmod +x $DOCKER_CONFIG/cli-plugins/docker-compose

$ 버전확인 
$ docker compose version
Docker Compose version v2.3.4

Home 폴더에 설치하는 것 말고는 뭐 크게 다르지 않은 것 같다.

V2는 docker-copose가 아니라 docker compose로 시작한다

 

2. docker-compose.yml 파일 작성

설치는 비교적 간단하고 사용법도 어렵지 않다. 

https://www.composerize.com/ 해당 사이트를 이용하면 docker run 명령어를 compose파일로 만들어준다.

 

Composerize

 

www.composerize.com

야믈파일은 버전, 서비스, 볼륨, 네트워크 4가지 항목으로 구성되어있다.

들여 쓰기를 통해 하위 항목을 구분한다.

샘플은 wildfly dockerfile로 만들어본다.

https://minggu92.tistory.com/79

 

[Docker] wildfly Dockerfile을 이용한 Container 생성

[Docker] wildfly Dockerfile을 이용한 Container 생성 docker로 작업할 때 가장 효율이 나는 것들 중 하나는 바로 웹서버이다. nginx 띄우는 예제는 많지만 wildfly를 더 많이 사용해왔기 때문에 wildfly contai..

minggu92.tistory.com

 

2.1) docker-compose.yml 예시

# docker-compose.yml

# docker-compose 버전명시
version: "3.8" # optional since v1.27.0

# 사용할 image들 즉, 생성할 컨테이너
services: 
  # 생성할 컨테이너 이름... 하지만 조금 다른 이름으로 생성됨
  <container-name>:
    # 컨테이너 생성에 사용할 이미지
    image: node:10
    # WORKDIR ( -w )
    working_dir: /usr/src/app
    # 포트포워딩 ( -p )
    ports:
      - 3000:3000
    # docker build
    build: 
      # 사용할 Dockerfile위치
      context: ./
      # 사용할 Dockerfile이름
      dockerfile: Dockerfile.dev
    # volume지정 ( -v )
    volumes: 
      # node_modules는 컨테이너의 workdir에서 사용
      - /usr/src/app/node_modules
      # 이외에 모든 파일 참조
      - ./:/usr/src/app

 

2.2) version

docker engine의 버전에 따라 docker-compose 버전도 달라지는데 

 

$ docker -v
Docker version 20.10.14, build a224086

현재 docker 버전이 20대이기 때문에 3.8로 설정하면 되겠다.

근데 선택사항이니 필수로 적을 필요는 없다~!

 

2.3) services

각 컨테이너에서 적용될 생성 옵션을 지정한다. 

서비스는 도커 컴포즈로 생성할 컨테이너 옵션을 정의한다. 이 항목에 쓰인 각 서비스는 컨테이너로 구현되며, 하나의 프로젝트로서 도커 컴포즈에 의해 관리된다. 서비스의 이름은 services의 하위 항목으로 정의하고 컨테이너의 옵션은 서비스 이름의 하위 항목에 정의한다.

  • image: 서비스의 컨테이너를 생성할 때 쓰일 이미지의 이름을 설정.
    이미지 이름 포맷은 docker run의 [저장 소이름]/[이미지 이름]:[태그] 형태와 같으며, 존재하지 않을 경우 저장소에서 자동으로 내려받는다.
  • links: —link와 동일하며, 다른 서비스에 서비스명만으로 접근할 수 있도록 설정.
    [SERVICE:ALIAS]의 형식으로 별칭으로 서비스에 접근할 수도 있다.
  • environment: —env, -e 옵션과 동일하며, 컨테이너 내부에서 사용할 환경변수 설정.
  • command: run 명령어의 마지막에 붙는 커맨드와 같으며, 컨테이너가 실행될 때 수행할 명령어를 설정.
  • depends_on: 특정 컨테이너에 대한 의존 관계를 나타내며, 이 항목에 명시된 컨테이너가 먼저 실행되고 해당 컨테이너가 실행된다.
    cf> —no -deps: 특정 서비스의 컨테이너만 생성하고, 의존성이 없는 컨테이너 생성 ($ docker-compose up —no-deps web)
  • ports: -p와 같으며, 서비스의 컨테이너를 개방할 포트를 설정.
    단일 호스트 환경에서 80:80과 같이 호스트의 특정 포트를 서비스의 컨테이너에 연결하면 docker-compose scale 명령어로 서비스의 컨테이너의 수를 늘릴 수 없다
  • build: 도커 파일에서 이미지를 빌드해 서비스의 컨테이너를 생성하도록 설정.
  • extends: 다른 YAML 파일이나 현재 YAML 파일에서 서비스 속성을 상속 받음.

 

2.4) networks

  • driver: 서비스의 컨테이너가 브리지 네트워크가 아닌 다른 네트워크를 사용하도록 설정할 수 있다.
  • ipam: IPAM(IP Address Manager)를 위해 사용할 수 있는 옵션으로 subnet, ip 범위 등을 설정할 수 있다.
  • external: YAML 파일을 통해 프로젝트를 생성할 때마다 네트워크를 생성하는 것이 아닌 기존의 네트워크를 사용하도록 설정할 수 있다.

네트워크 항목을 정의하지 않으면, 도커 컴포즈는 프로젝트별로 브리지 타입의 네트워크를 생성한다.

생성된 네트워크의 이름은 [프로젝트 이름]_default

생성 docker-compose up / 삭제 docker-compose down

 

2.5) volumes

  • driver: 볼륨을 생성할 때 사용될 드라이버를 설정한다. 어떠한 설정도 하지 않으면 local로 설정되며 사용하는 드라이버에 따라 변경해야 한다.
  • external: 프로젝트를 생성할 때마다 볼륨을 매번 생성하지 않고 기존 볼륨을 사용할 수 있도록 설정한다.

 

2.6) 파일 검증하기

docker-compose config 명령어를 사용하여, 오타나 파일 포맷이 적절한지 등에 대한 검사를 진행할 수 있다.

기본적으로 현재 디렉터리의 파일을 검사 (docker-compose -f [yml파일 경로] config로 변경 가능)



출처: https://sohyun-lee.tistory.com/12 [sohyun's]

 

3. docker-compose.yml 최종

# docker-compose.yml

version: "3.8"

services: 
  wildflyC:
    build: 
      context: ./
      dockerfile: wildfly.Dockerfile  
    image: skylark-wildfly
    ports:
      - 1003:8080
    volumes:
      - '/opt/docker/volume/wildfly/logs:/opt/jboss/wildfly/standalone/log'
      - '/opt/docker/volume/wildfly/deployments:/opt/jboss/wildfly/standalone/deployments'

 

 

4. 실행

$ docker compose up --build

# 백그라운드에서 특정 yml 파일 실행
$ docker compose -f /opt/docker/docker-compose.yml up {myService} -d
  • --build: 실행할 때마다 새로 빌드하기 ( 변경사항 적용 )
  • -d : 백그라운드로 실행한다. 실제 서버는 -d로 띄우자 
  • -f : docker-compose.yml 파일명이 다를 때 파일 선택 옵션

특정 형식에 맞춰서 컨테이너 이름이 자동적으로 생성됨

 

docker compose up --build
[+] Building 63.1s (11/11) FINISHED
 => [internal] load build definition from wildfly.Dockerfile                                                                                                                     0.4s
 => => transferring dockerfile: 97B                                                                                                                                              0.0s
 => [internal] load .dockerignore                                                                                                                                                0.5s
 => => transferring context: 2B                                                                                                                                                  0.0s
 => [internal] load metadata for docker.io/jboss/base-jdk:8                                                                                                                      0.0s
 => [internal] load build context                                                                                                                                                8.9s
 => => transferring context: 393.77MB                                                                                                                                            8.2s
 => CACHED [1/6] FROM docker.io/jboss/base-jdk:8                                                                                                                                 0.0s
 => [2/6] RUN cd $HOME     && curl -L -O https://github.com/wildfly/wildfly/releases/download/26.0.1.Final/wildfly-26.0.1.Final.tar.gz     && sha1sum wildfly-26.0.1.Final.tar  35.7s
 => [3/6] RUN mkdir -p /opt/jboss/wildfly/modules/system/layers/base/org/postgresql/main     && cd "$_"     && curl -sLO https://repo1.maven.org/maven2/org/postgresql/postgres  3.5s
 => [4/6] ADD resources/module.xml /opt/jboss/wildfly/modules/system/layers/base/org/postgresql/main/module.xml                                                                  0.9s
 => [5/6] ADD resources/standalone.xml /opt/jboss/wildfly/standalone/configuration/standalone.xml                                                                                0.7s
 => [6/6] ADD resources/skylark-vbn.war /opt/jboss/wildfly/standalone/deployments/                                                                                               7.7s
 => exporting to image                                                                                                                                                          13.6s
 => => exporting layers                                                                                                                                                         13.2s
 => => writing image sha256:825c5d90f46a9f505f103df186aba7ba535eb21cbad9f010237500c128aa8ac9                                                                                     0.0s
 => => naming to docker.io/library/skylark-wildfly                                                                                                                               0.0s

Use 'docker scan' to run Snyk tests against images to find vulnerabilities and learn how to fix them
[+] Running 2/2
 ⠿ Network wildfly_default       Created                                                                                                                                         0.9s
 ⠿ Container wildfly-wildflyC-1  Created                                                                                                                                         0.6s
Attaching to wildfly-wildflyC-1
wildfly-wildflyC-1  | =========================================================================
...

체감상 Dockerfile 돌리는 것보다 훨씬 빨랐다!!! 그리고 캐시때문인지 다시 빌드할때도 엄청나게 빠르다 ! 심지어 테스트까지 해주다니 이것 참 좋은 녀석이군

 

볼륨 설정한 폴더에 정상적으로 로그가 쌓인다.

 

5. 실행 후

백그라운드에서 실행한 후 로그를 확인하기 위해선 logs 명령어를 사용한다.

# jenkins라는 service의 log를 실시간 확인
$ docker compose logs -f jenkins

 

해당 컨테이너에서 명령어를 던지기 위해 run 이나 exec를 사용한다

$ docker compose run wildfly uname -a
Linux 048f0dd6a456 3.10.0-1160.49.1.el7.x86_64 #1 SMP Tue Nov 30 15:51:32 UTC 2021 x86_64 x86_64 x86_64 GNU/Linux

 

 

 

사용한 Dockerfile

# Use latest jboss/base-jdk:8 image as the base
FROM jboss/base-jdk:8

#metadata
LABEL name="minggu"
LABEL email="minha1002@gmail.com"
LABEL description="wildfly test"
FROM jboss/base-jdk:8

# Set the WILDFLY_VERSION env variable
ENV WILDFLY_VERSION 26.0.1.Final
ENV WILDFLY_SHA1 08908faf9ae99e5fb6374979afbffea461aadc2c
ENV JBOSS_HOME /opt/jboss/wildfly

USER root

# Add the WildFly distribution to /opt, and make wildfly the owner of the extracted tar content
# Make sure the distribution is available from a well-known place
RUN cd $HOME \
    && curl -L -O https://github.com/wildfly/wildfly/releases/download/$WILDFLY_VERSION/wildfly-$WILDFLY_VERSION.tar.gz \
    && sha1sum wildfly-$WILDFLY_VERSION.tar.gz | grep $WILDFLY_SHA1 \
    && tar xf wildfly-$WILDFLY_VERSION.tar.gz \
    && mv $HOME/wildfly-$WILDFLY_VERSION $JBOSS_HOME \
    && rm wildfly-$WILDFLY_VERSION.tar.gz \
    && chown -R jboss:0 ${JBOSS_HOME} \
    && chmod -R g+rw ${JBOSS_HOME}

# Install postgreSQL Driver
RUN mkdir -p ${JBOSS_HOME}/modules/system/layers/base/org/postgresql/main \
    && cd "$_" \
    && curl -sLO https://repo1.maven.org/maven2/org/postgresql/postgresql/42.3.3/postgresql-42.3.3.jar	


# Change resources
ADD resources/module.xml ${JBOSS_HOME}/modules/system/layers/base/org/postgresql/main/module.xml
ADD resources/standalone.xml ${JBOSS_HOME}/standalone/configuration/standalone.xml

# Add war file
# volume ADD Crush
#ADD resources/skylark-vbn.war ${JBOSS_HOME}/standalone/deployments/


# Add wildfly admin account
USER jboss
#RUN bash -c "${JBOSS_HOME}/bin/add-user.sh admin minggu"
RUN /opt/jboss/wildfly/bin/add-user.sh -u admin -p minggu #admin --silent

# Make volume directory
# docker run -v is better than VOLUME
#VOLUME /opt/wildfly_docker/logs
#VOLUME ${JBOSS_HOME}/standalone/deployments

# Ensure signals are forwarded to the JVM process correctly for graceful shutdown
ENV LAUNCH_JBOSS_IN_BACKGROUND true

# Expose the ports in which we're interested
EXPOSE 8080 9990

# Set the default command to run on boot
# This will boot WildFly in standalone mode and bind to all interfaces
CMD /opt/jboss/wildfly/bin/standalone.sh -c standalone.xml -b 0.0.0.0 -bmanagement 0.0.0.0 --debug
#CMD ["/opt/jboss/wildfly/bin/standalone.sh", "-b", "0.0.0.0"]

 

 

<참고>

https://github.com/docker/compose

 

GitHub - docker/compose: Define and run multi-container applications with Docker

Define and run multi-container applications with Docker - GitHub - docker/compose: Define and run multi-container applications with Docker

github.com

 

https://docs.docker.com/compose/

 

Overview of Docker Compose

 

docs.docker.com

https://meetup.toast.com/posts/277

 

Docker Compose와 버전별 특징 : NHN Cloud Meetup

도커는 이제 대부분의 개발자 노트북이나 PC에 하나씩은 설치되어있는 필수품이 되어가는데요 편하고 유용한 도커를 좀 더 유익하고 편하게 사용할 수 있는 도구인 Docker Compose에 대해서 알기 쉽

meetup.toast.com

https://sohyun-lee.tistory.com/12

 

[Docker] Docker compose 설치 및 사용방법

이 페이지는 도커 컴포즈의 설치 및 사용방법에 대해 설명하고 있다. 도커 컴포즈 (Docker Compose)란? 여러 개의 컨테이너가 하나의 애플리케이션으로 동작할 때, 이를 테스트하려면 각 컨테이너를

sohyun-lee.tistory.com

 

[Android / Java] Cloud Firestore 연동을 통한 Data 조작(feat. RecyclerView)

 

Cloud Firestore는 Firebase 및 Google Cloud의 모바일, 웹, 서버 개발에 사용되는 유연하고 확장 가능한 데이터베이스입니다. Firebase 실시간 데이터베이스와 마찬가지로 실시간 리스너를 통해 클라이언트 애플리케이션 간에 데이터의 동기화를 유지하고 모바일 및 웹에 대한 오프라인 지원을 제공해 네트워크 지연 시간이나 인터넷 연결에 상관없이 원활하게 반응하는 앱을 개발할 수 있습니다. Cloud Firestore는 Cloud Functions를 비롯한 다른 Firebase 및 Google Cloud 제품과도 원활하게 통합됩니다.  -구글

 

자자 안드로이드 할 거면 파이어 스토어는 구축해봐야겠지.

공식문서와 함께 firestore을 구축해보자. 물론 하단엔 sample github도 첨부하겠다.

https://github.com/minha9012/Firestore-sample

이번 포스팅에서 처음 사용해본 기술

1. Cloud Firestore

2. RecyclerView

3. ViewBinding

 

 

1. Firebase 프로젝트 생성

저번에 만든 firebase sample앱에서 이어서 만들어서 따라 해도 된다. 그게 더 편하겠지만 나는 새 프로젝트를 생성해서 작업해보겠다. firebase 프로젝트 생성 및 연동은 아래 글을 참조하자.

https://minggu92.tistory.com/75

 

[Android] firebase 연동 (최신버전)

[Android] firebase 연동 오랜만에 돌아온 안드로이드 시간. 원래는 블로그를 내가 자주 잊을 수 있는 안드로이드 위주로만 올리려고 하다가.. 웹 개발 쪽 이것저것 올리다 보니 소홀해졌었다. 따라서

minggu92.tistory.com

 

 

2. Gradle 추가

2.1) module 수준 Gradle

// Top-level build file where you can add configuration options common to all sub-projects/modules.
plugins {
    id 'com.android.application' version '7.1.2' apply false
    id 'com.android.library' version '7.1.2' apply false
    //구글 서비스 추가
    id 'com.google.gms.google-services' version '4.3.10' apply false
}

task clean(type: Delete) {
    delete rootProject.buildDir
}

 

2.2) app 수준 Gradle

plugins {
    id 'com.android.application'
    id 'com.google.gms.google-services' //구글서비스 추가
}

android {
    compileSdk 32

    defaultConfig {
        applicationId "com.example.firestore_sample"
        minSdk 21
        targetSdk 32
        versionCode 1
        versionName "1.0"

        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }

    buildFeatures {
        viewBinding = true
    }
}

dependencies {

    implementation 'androidx.appcompat:appcompat:1.4.1'
    implementation 'com.google.android.material:material:1.5.0'
    implementation 'androidx.constraintlayout:constraintlayout:2.1.3'
    testImplementation 'junit:junit:4.13.2'
    androidTestImplementation 'androidx.test.ext:junit:1.1.3'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'

    // Import the BoM for the Firebase platform
    implementation platform('com.google.firebase:firebase-bom:29.2.1')

    // Declare the dependency for the Cloud Firestore library
    // When using the BoM, you don't specify versions in Firebase library dependencies
    implementation 'com.google.firebase:firebase-firestore'
}

추가할 건 별로 없다.

 

 

3. Firestore 컬렉션 추가

Cloud Firestore는 NoSQL 문서 중심의 데이터베이스입니다. SQL 데이터베이스와 달리 테이블이나 행이 없으며, 컬렉션으로 정리되는 문서에 데이터를 저장합니다.

각 문서에는 키-값 쌍이 들어 있습니다. Cloud Firestore는 작은 문서가 많이 모인 컬렉션을 저장하는 데 최적화되어 있습니다.

모든 문서는 컬렉션에 저장되어야 합니다. 문서는 하위 컬렉션 및 중첩 객체를 포함할 수 있으며, 둘 다 문자열 같은 기본형 필드나 목록 같은 복합 객체를 포함할 수 있습니다.

컬렉션과 문서는 Cloud Firestore에서 암시적으로 생성됩니다. 사용자는 컬렉션 내의 문서에 데이터를 할당하기만 하면 됩니다. 컬렉션 또는 문서가 없으면 Cloud Firestore에서 자동으로 생성합니다.

 

Firestore Instance를 앱 내에 선언하고 명시적으로 Collection과 Documentation을 추가해 줄 수도 있고 FIrebase콘솔에서 생성해 줄 수도 있다고 하는데 일단 우리는 콘솔에서 새 Collection을 하나 만들어보자.

 

서버는 eastasia를 선택했다.

 

컬렉션 시작을 눌러주고

 

별다른 설명 없이도 너무나 쉽다.. 

 

4. Layout 그리기

단순 데이터를 조작하는 것도 좋지만 뷰를 띄워서 실제로 동작하는 것을 보면 더더욱 공부가 될 테니 간단하게나마 Recycler View의 Layout을 그려보도록 하자. 

4.1) activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.appcompat.widget.LinearLayoutCompat
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MainActivity">

    <androidx.appcompat.widget.LinearLayoutCompat
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">

        <Button
            android:id="@+id/btn_write"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:layout_margin="10dp"
            android:text="유저 추가"/>


        <Button
            android:id="@+id/btn_reload"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:layout_margin="10dp"
            android:text="새로고침" />

        <Button
            android:id="@+id/btn_delete"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_margin="10dp"
            android:layout_weight="1"
            android:text="유저 삭제" />
    </androidx.appcompat.widget.LinearLayoutCompat>

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/rv_user_list"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</androidx.appcompat.widget.LinearLayoutCompat>

미스터심플심플심플어쩌구

 

4.2) rv_layout.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_margin="4dp"
    app:cardCornerRadius="8dp">

    <androidx.appcompat.widget.LinearLayoutCompat
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">


        <TextView
            android:id="@+id/tv_first_name"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_margin="10dp"
            android:layout_weight="1"
            android:text="firstName" />

        <TextView
            android:id="@+id/tv_last_name"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_margin="10dp"
            android:layout_weight="1"
            android:text="LastName" />

        <TextView
            android:id="@+id/tv_born"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_margin="10dp"
            android:layout_weight="1"
            android:text="Born" />
    </androidx.appcompat.widget.LinearLayoutCompat>
    
</androidx.cardview.widget.CardView>

 

 

5. Java 소스 만들기

5.1) UserModel.java

package com.example.firestore_sample;

public class UserModel {

    String firstName;
    String lastName;
    int born;

    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public String getLastName() {
        return lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

    public int getBorn() {
        return born;
    }

    public void setBorn(int born) {
        this.born = born;
    }
}

generater 못 잃어ㅜ

 

5.2) userRecyclerViewAdapter.java

package com.example.firestore_sample;

import android.annotation.SuppressLint;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;

import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;

import java.util.ArrayList;

public class userRecyclerViewAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
    String TAG = "userRecyclerViewAdapter";

    ArrayList<UserModel> userModels;

    public userRecyclerViewAdapter(ArrayList<UserModel> userModels) {
        this.userModels = userModels;
    }

    @NonNull
    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        //자신이 만든 itemview를 inflate한 다음 뷰홀더 생성
        View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.rv_layout, parent, false);
        //생선된 뷰홀더를 리턴하여 onBindViewHolder에 전달한다.
        return new ViewHolder(view);
    }

    @SuppressLint("SetTextI18n")
    @Override
    public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
        ViewHolder viewHolder = (ViewHolder) holder;
        viewHolder.tvFirstName.setText(userModels.get(position).getFirstName());
        viewHolder.tvLastName.setText(userModels.get(position).getLastName());
        viewHolder.tvBorn.setText( Integer.toString(userModels.get(position).getBorn()));
    }

    @Override
    public int getItemCount() {
        return userModels.size();
    }

    class ViewHolder extends RecyclerView.ViewHolder {
        TextView tvFirstName;
        TextView tvLastName;
        TextView tvBorn;

        public ViewHolder(@NonNull View itemView) {
            super(itemView);
            tvFirstName = itemView.findViewById(R.id.tv_first_name);
            tvLastName = itemView.findViewById(R.id.tv_last_name);
            tvBorn = itemView.findViewById(R.id.tv_born);
        }
    }
}

 

5.3) MainActivity.java

package com.example.firestore_sample;

import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;

import android.annotation.SuppressLint;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.os.Bundle;
import android.util.Log;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Toast;

import com.example.firestore_sample.databinding.ActivityMainBinding;
import com.google.android.gms.tasks.OnFailureListener;
import com.google.android.gms.tasks.OnSuccessListener;
import com.google.firebase.firestore.DocumentReference;
import com.google.firebase.firestore.FirebaseFirestore;
import com.google.firebase.firestore.QueryDocumentSnapshot;
import com.google.firebase.firestore.QuerySnapshot;
import com.google.firebase.firestore.auth.User;
import com.google.firebase.firestore.model.Document;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;

public class MainActivity extends AppCompatActivity {

    private static final String TAG = "MainActivity";

    userRecyclerViewAdapter adapter; //리싸이클러 뷰 어댑터
    RecyclerView recyclerView; //리싸이클러 뷰
    ArrayList<UserModel> userList = new ArrayList<>();
    FirebaseFirestore db = FirebaseFirestore.getInstance(); // Access a Cloud Firestore instance from your Activity

    @SuppressLint("NotifyDataSetChanged")
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ActivityMainBinding binding = ActivityMainBinding.inflate(getLayoutInflater());
        setContentView(binding.getRoot());

        adapter = new userRecyclerViewAdapter(userList); //initialize Adapter

        binding.rvUserList.setLayoutManager(new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false));
        binding.rvUserList.setAdapter(adapter);
        binding.btnReload.setOnClickListener(view -> { //새로고침 버튼
            db.collection("users")
                    .get()
                    .addOnSuccessListener(e -> {
                        userList.clear();
                        for (QueryDocumentSnapshot document : e) {
                            UserModel user = new UserModel((String) document.get("firstName")
                                    , (String) document.get("lastName")
                                    , (Long) document.get("born")
                            );
                            userList.add(user);
                        }
                        adapter.notifyDataSetChanged();
                        Toast.makeText(this, "유저 데이터 로드 성공", Toast.LENGTH_SHORT).show();
                    })
                    .addOnFailureListener(exception -> {
                        Toast.makeText(this, "데이터 로드 실패", Toast.LENGTH_SHORT).show();
                        Log.w(TAG, "error! " + exception);
                    });
        });

        binding.btnWrite.setOnClickListener(view -> { //유저 등록
            //동적으로 AlertDialog 생성
            AlertDialog.Builder builder = new AlertDialog.Builder(this);

            TextView tvFirstName = new TextView(this);
            TextView tvLastName = new TextView(this);
            TextView tvBorn = new TextView(this);
            tvFirstName.setText("firstName");
            tvLastName.setText("lastName");
            tvBorn.setText("born");

            EditText etFirstName = new EditText(this);
            EditText etLastName = new EditText(this);
            EditText etBorn = new EditText(this);
            etFirstName.setSingleLine(true);
            etLastName.setSingleLine(true);

            LinearLayout layout = new LinearLayout(this);
            layout.setOrientation(LinearLayout.VERTICAL);
            layout.setPadding(16, 16, 16, 16);
            layout.addView(tvFirstName);
            layout.addView(etFirstName);
            layout.addView(tvLastName);
            layout.addView(etLastName);
            layout.addView(tvBorn);
            layout.addView(etBorn);

            builder.setView(layout)
                    .setTitle("유저 추가")
                    .setPositiveButton(R.string.add, (dialogInterface, i) -> {
                        UserModel user = new UserModel(etFirstName.getText().toString()
                                , etLastName.getText().toString()
                                , Long.parseLong(etBorn.getText().toString())
                        );

                        db.collection("users")
                                .add(user)
                                .addOnSuccessListener(e -> {
                                    Toast.makeText(this, "유저 추가 성공", Toast.LENGTH_SHORT).show();
                                })
                                .addOnFailureListener(exception -> {
                                    Toast.makeText(this, "유저 추가 실패", Toast.LENGTH_SHORT).show();
                                    Log.w(TAG, "error! " + exception);
                                });

                        binding.btnReload.callOnClick(); //새로고침 버튼 클릭

                    })
                    .setNegativeButton(R.string.cancel, (dialogInterface, i) -> {

                    })
                    .show();
        });

        binding.btnDelete.setOnClickListener(view -> { //유저삭제
            db.collection("user")
                    .document()
                    .delete()
                    .addOnSuccessListener(e -> {
                        binding.btnReload.callOnClick(); //새로고침 버튼 클릭
                        Toast.makeText(this, "유저 삭제 성공", Toast.LENGTH_SHORT).show();
                    })
                    .addOnFailureListener(exception -> {
                        Toast.makeText(this, "유저 삭제 실패", Toast.LENGTH_SHORT).show();
                        Log.w(TAG, "error! " + exception);
                    });
        });

    }

}

메인 액티비티부터 공개했다. 자세한 내용은 아래를 따라서

 

6. Data 조작

5.1) Cloud Firebase Instance 초기화

// Access a Cloud Firestore instance from your Activity
Firestore firestore = new Firestore(FirebaseFirestore.getInstance());

 

5.2) Data 추가

Cloud Firestore는 컬렉션에 저장되는 문서에 데이터를 저장합니다. 문서에 데이터를 처음 추가할 때 Cloud Firestore에서 암시적으로 컬렉션과 문서를 만듭니다. 컬렉션이나 문서를 명시적으로 만들 필요가 없습니다.

다음 예시 코드를 사용해 새 컬렉션과 문서를 만듭니다.

// Add a new document with a generated ID
// 새 컬렉션과 문서 추가
db.collection("users")
        .add(user)
        .addOnSuccessListener(documentReference -> Log.d(TAG, "DocumentSnapshot added with ID: " + documentReference.getId()))
        .addOnFailureListener(e -> Log.w(TAG, "Error adding document", e));
        
//하위 컬렉션의 메시지에 대한 참조를 만들 수 있습니다.        
DocumentReference messageRef = db
        .collection("rooms").document("roomA")
        .collection("messages").document("message1");
        
        
        
//난 다음과 같이 구현했다.
db.collection("users")
        .add(user)
        .addOnSuccessListener(e -> {
            Toast.makeText(this, "유저 추가 성공", Toast.LENGTH_SHORT).show();
        })
        .addOnFailureListener(exception -> {
            Toast.makeText(this, "유저 추가 실패", Toast.LENGTH_SHORT).show();
            Log.w(TAG, "error! " + exception);
        });

혹은 유저를 추가하는 함수를 만들 수도 있겠다.

 

5.3) Data 읽기

db.collection("users")
        .get()
        .addOnCompleteListener(task -> {
            if (task.isSuccessful()) {
                for (QueryDocumentSnapshot document : task.getResult()) {
                    Log.d(TAG, document.getId() + " => " + document.getData());
                }
            } else {
                Log.w(TAG, "Error getting documents.", task.getException());
            }
        });
        
//난 이렇게 구현했다.
db.collection("users")
        .get()
        .addOnSuccessListener(e -> {
            userList.clear();
            for (QueryDocumentSnapshot document : e) {
                UserModel user = new UserModel((String) document.get("firstName")
                        , (String) document.get("lastName")
                        , (Long) document.get("born")
                );
                userList.add(user);
            }
            adapter.notifyDataSetChanged();
            Toast.makeText(this, "유저 데이터 로드 성공", Toast.LENGTH_SHORT).show();
        })
        .addOnFailureListener(exception -> {
            Toast.makeText(this, "데이터 로드 실패", Toast.LENGTH_SHORT).show();
            Log.w(TAG, "error! " + exception);
        });

 

 

6. Firebase Console에서 보안 규칙 추가

웹, Android 또는 Apple 플랫폼 SDK를 사용하는 경우 Firebase 인증 및 Cloud Firestore 보안 규칙을 사용하여 Cloud Firestore의 데이터에 보안을 적용합니다.

다음은 시작하는 데 사용할 수 있는 기본 규칙 세트입니다. Console의 규칙 탭에서 보안 규칙을 수정할 수 있습니다.

 

// Allow read/write access on all documents to any user signed in to the application
service cloud.firestore {
  match /databases/{database}/documents {
    match /{document=**} {
    	//테스트
        allow read, write: if true;
      //allow read, write: if request.auth != null;
    }
  }
}

인증과 함께 규칙을 설정하면 database를 다루는데 보안성이 강화될 것이다.

 

7. 최종 Test

7.1) 앱 최초 실행

 

7.2) 새로고침 버튼 클릭

 

7.3) 유저 추가 버튼 클릭

 

정상적으로 들어오는 것을 확인했다.

 

7.4) 유저 삭제

삭제는 읽기나 추가처럼 collection만 참조해서는 안되고 document 자체를 참조해야 하는데 id값을 받아와야 구현이 가능하겠다. 

그리고 컬렉션 삭제는 adroid에서는 불가능!

 

 

 

 

 

<참고>

https://firebase.google.com/docs/firestore/quickstart?authuser=0#java 

 

Cloud Firestore 시작하기  |  Firebase Documentation

Join Firebase at Google I/O online May 11-12, 2022. Register now 의견 보내기 Cloud Firestore 시작하기 이 빠른 시작에서는 Cloud Firestore를 설정하고 데이터를 추가한 후 Firebase Console에서 방금 추가한 데이터를 확인

firebase.google.com

 

솔직히 도커는 고래이미지가 더 귀엽지 않나...쩝

[Docker] wildfly Dockerfile을 이용한 Container 생성 (+Admin)

 

docker로 작업할 때 가장 효율이 나는 것들 중 하나는 바로 웹서버이다.

nginx 띄우는 예제는 많지만 wildfly를 더 많이 사용해왔기 때문에 wildfly container를 만들어 띄워보는 것을 해보겠다.

 

1. Host OS 방화벽 설정

#새로운 포트 개방
[minggu92@cloud dockerfiles]$ firewall-cmd --add-port=1003/tcp --permanent
SUCCESS!
[minggu92@cloud dockerfiles]$ firewall-cmd --reload

 

2. wildfly에서 제공하는 Dockerfile

# Use latest jboss/base-jdk:11 image as the base
FROM jboss/base-jdk:11

# Set the WILDFLY_VERSION env variable
ENV WILDFLY_VERSION 26.0.1.Final
ENV WILDFLY_SHA1 08908faf9ae99e5fb6374979afbffea461aadc2c
ENV JBOSS_HOME /opt/jboss/wildfly

USER root

# Add the WildFly distribution to /opt, and make wildfly the owner of the extracted tar content
# Make sure the distribution is available from a well-known place
RUN cd $HOME \
    && curl -L -O https://github.com/wildfly/wildfly/releases/download/$WILDFLY_VERSION/wildfly-$WILDFLY_VERSION.tar.gz \
    && sha1sum wildfly-$WILDFLY_VERSION.tar.gz | grep $WILDFLY_SHA1 \
    && tar xf wildfly-$WILDFLY_VERSION.tar.gz \
    && mv $HOME/wildfly-$WILDFLY_VERSION $JBOSS_HOME \
    && rm wildfly-$WILDFLY_VERSION.tar.gz \
    && chown -R jboss:0 ${JBOSS_HOME} \
    && chmod -R g+rw ${JBOSS_HOME}

# Ensure signals are forwarded to the JVM process correctly for graceful shutdown
ENV LAUNCH_JBOSS_IN_BACKGROUND true

USER jboss

# Expose the ports in which we're interested
EXPOSE 8080

# Set the default command to run on boot
# This will boot WildFly in standalone mode and bind to all interfaces
CMD ["/opt/jboss/wildfly/bin/standalone.sh", "-b", "0.0.0.0"]

 

3. docker build

#docker build -t [name]:[tag] [경로] -f [*.Dockerfile] 
[minggu92@cloud dockerfiles]$ docker build -t wildfly:0.1 /dockerfiles/ -f /dockerfiles/wildfly.Dockerfile
Sending build context to Docker daemon   5.12kB
Step 1/10 : FROM jboss/base-jdk:8
8: Pulling from jboss/base-jdk
75f829a71a1c: Pull complete
7b11f246b3d3: Pull complete
b765648c2a58: Pull complete
3672c49587a4: Pull complete
Digest: sha256:4ce172c6e0874a5e1b38a90dd6eda148f3e2fa1d196cba95e5a32dc0809286c8
Status: Downloaded newer image for jboss/base-jdk:8
 ---> b0802b3eccf0
Step 2/10 : ENV WILDFLY_VERSION 26.0.1.Final
 ---> Running in c239c89152d2
Removing intermediate container c239c89152d2
 ---> 1f170fb475fe
Step 3/10 : ENV WILDFLY_SHA1 08908faf9ae99e5fb6374979afbffea461aadc2c
 ---> Running in ad87a7569133
Removing intermediate container ad87a7569133
 ---> caebe17f0f49
Step 4/10 : ENV JBOSS_HOME /opt/jboss/wildfly
 ---> Running in 1c32c740d2f8
Removing intermediate container 1c32c740d2f8
 ---> 5e1338c9a63b
Step 5/10 : USER root
 ---> Running in cbb71484eb19
Removing intermediate container cbb71484eb19
 ---> ebe0bc90a9df
Step 6/10 : RUN cd $HOME     && curl -L -O https://github.com/wildfly/wildfly/releases/download/$WILDFLY_VERSION/wildfly-$WILDFLY_VERSION.tar.gz     && sha1sum wildfly-$WILDFLY_VERSION.tar.gz | grep $WILDFLY_SHA1     && tar xf wildfly-$WILDFLY_VERSION.tar.gz     && mv $HOME/wildfly-$WILDFLY_VERSION $JBOSS_HOME     && rm wildfly-$WILDFLY_VERSION.tar.gz     && chown -R jboss:0 ${JBOSS_HOME}     && chmod -R g+rw ${JBOSS_HOME}
 ---> Running in 8efc34f6999f
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100   660  100   660    0     0    807      0 --:--:-- --:--:-- --:--:--   808
100  207M  100  207M    0     0   368k      0  0:09:36  0:09:36 --:--:-- 1355k
08908faf9ae99e5fb6374979afbffea461aadc2c  wildfly-26.0.1.Final.tar.gz
Removing intermediate container 8efc34f6999f
 ---> 700b6dc1974a
Step 7/10 : ENV LAUNCH_JBOSS_IN_BACKGROUND true
 ---> Running in 18d2acff9d47
Removing intermediate container 18d2acff9d47
 ---> 38b8214ce130
Step 8/10 : USER jboss
 ---> Running in 5306dfff81a9
Removing intermediate container 5306dfff81a9
 ---> e47f42fc981a
Step 9/10 : EXPOSE 8080
 ---> Running in caaa4e890999
Removing intermediate container caaa4e890999
 ---> f7ef1591a05c
Step 10/10 : CMD ["/opt/jboss/wildfly/bin/standalone.sh", "-b", "0.0.0.0"]
 ---> Running in 0dccf31d8ec2
Removing intermediate container 0dccf31d8ec2
 ---> 6725a1d25a62
Successfully built 6725a1d25a62
Successfully tagged wildfly:0.1


4. docker run

#생성된 docker image 확인
[minggu92@cloud dockerfiles]$ docker images
REPOSITORY       TAG       IMAGE ID       CREATED          SIZE
wildfly          0.1       6725a1d25a62   13 minutes ago   757MB
jboss/base-jdk   8         b0802b3eccf0   18 months ago    495MB



#docker container 실행
[minggu92@cloud dockerfiles]$ docker run -p 1003:8080 wildfly:0.1
=========================================================================

  JBoss Bootstrap Environment

  JBOSS_HOME: /opt/jboss/wildfly

  JAVA: /usr/lib/jvm/java/bin/java

  JAVA_OPTS:  -server -Xms64m -Xmx512m -XX:MetaspaceSize=96M -XX:MaxMetaspaceSize=256m -Djava.net.preferIPv4Stack=true -Djboss.modules.system.pkgs=org.jboss.byteman -Djava.awt.headless=true

=========================================================================

04:45:12,433 INFO  [org.jboss.modules] (main) JBoss Modules version 2.0.0.Final
04:45:13,419 INFO  [org.jboss.msc] (main) JBoss MSC version 1.4.13.Final
04:45:13,445 INFO  [org.jboss.threads] (main) JBoss Threads version 2.4.0.Final
....

....
 8417ms - Started 298 of 538 services (337 services are lazy, passive or on-demand)
04:45:20,001 INFO  [org.jboss.as] (Controller Boot Thread) WFLYSRV0060: Http management interface listening on http://127.0.0.1:9990/management
04:45:20,001 INFO  [org.jboss.as] (Controller Boot Thread) WFLYSRV0051: Admin console listening on http://127.0.0.1:9990

 

5. 확인

일단 서버가 기동 되는 것을 확인, 이제 상황에 맞게 커스터마이징 하면 된다.

 

6. Custormizing

# Use latest jboss/base-jdk:8 image as the base
FROM jboss/base-jdk:8

#metadata
LABEL name="minggu"
LABEL email="minha1002@gmail.com"
LABEL description="wildfly test"
FROM jboss/base-jdk:8

# Set the WILDFLY_VERSION env variable
ENV WILDFLY_VERSION 26.0.1.Final
ENV WILDFLY_SHA1 08908faf9ae99e5fb6374979afbffea461aadc2c
ENV JBOSS_HOME /opt/jboss/wildfly

USER root

# Add the WildFly distribution to /opt, and make wildfly the owner of the extracted tar content
# Make sure the distribution is available from a well-known place
RUN cd $HOME \
    && curl -L -O https://github.com/wildfly/wildfly/releases/download/$WILDFLY_VERSION/wildfly-$WILDFLY_VERSION.tar.gz \
    && sha1sum wildfly-$WILDFLY_VERSION.tar.gz | grep $WILDFLY_SHA1 \
    && tar xf wildfly-$WILDFLY_VERSION.tar.gz \
    && mv $HOME/wildfly-$WILDFLY_VERSION $JBOSS_HOME \
    && rm wildfly-$WILDFLY_VERSION.tar.gz \
    && chown -R jboss:0 ${JBOSS_HOME} \
    && chmod -R g+rw ${JBOSS_HOME}

# Install postgreSQL Driver
RUN mkdir -p ${JBOSS_HOME}/modules/system/layers/base/org/postgresql/main \
    && cd "$_" \
    && curl -sLO https://repo1.maven.org/maven2/org/postgresql/postgresql/42.3.3/postgresql-42.3.3.jar	


# Change resources
ADD resources/module.xml ${JBOSS_HOME}/modules/system/layers/base/org/postgresql/main/module.xml
ADD resources/standalone.xml ${JBOSS_HOME}/standalone/configuration/standalone.xml

# Add war file
# volume ADD Crush
#ADD resources/skylark-vbn.war ${JBOSS_HOME}/standalone/deployments/


# Add wildfly admin account
USER jboss
#RUN bash -c "${JBOSS_HOME}/bin/add-user.sh admin minggu"
RUN /opt/jboss/wildfly/bin/add-user.sh -u admin -p minggu #admin --silent

# Make volume directory
# docker run -v is better than VOLUME
#VOLUME /opt/wildfly_docker/logs
#VOLUME ${JBOSS_HOME}/standalone/deployments

# Ensure signals are forwarded to the JVM process correctly for graceful shutdown
ENV LAUNCH_JBOSS_IN_BACKGROUND true

# Expose the ports in which we're interested
EXPOSE 8080 9990

# Set the default command to run on boot
# This will boot WildFly in standalone mode and bind to all interfaces
CMD /opt/jboss/wildfly/bin/standalone.sh -c standalone.xml -b 0.0.0.0 -bmanagement 0.0.0.0 --debug
#CMD ["/opt/jboss/wildfly/bin/standalone.sh", "-b", "0.0.0.0"]

미리 module을 정의한 standalone.xml을 준비해서 실행하면 된다

 

팁! ADD 로 war 경로를 설정했지만 volume으로 선언하면 war가 add되지 않는다.

volume설정된 폴더로 war를 옮겨주자

 

Admin 페이지도 성공!

 

 

<참고>

https://github.com/jboss-dockerfiles/wildfly

 

GitHub - jboss-dockerfiles/wildfly: Docker image for WildFly project

Docker image for WildFly project. Contribute to jboss-dockerfiles/wildfly development by creating an account on GitHub.

github.com

https://colinch4.github.io/2020-12-03/README.docker/

 

colin's 블로그

개발 강좌 블로그

colinch4.github.io

 

[Android] wifi를 통한 무선 디버깅 (ADB)

 

매번 안드로이드 스튜디오를 이용할 때 케이블을 준비해야 했다. 

그리고 기기를 연결해 디버그 콘솔을 본다거나, 내장 storage를 탐색했다.

그러다가 wifi adb 라는 것이 생기면서 같은 wifi를 이용하면 무선으로 기기를 연결할 수 있는 플러그인이 새로 나왔었는데 안정성 문제도 있고 보안 문제도 있고..

그리고 지금! 안드로이드 스튜디오 최신 버전을 사용하면서(범블비 버전) 기본적으로 이제 adb가 내장되어 wifi connection을 지원한다는 것을 알게 되었다.

옛날처럼 adb 포트 열고 커넥션 맺고 그럴 필요가 없다! 아주 쉽다!

 

 

1. 개발자옵션

Android 폰에 설정 - 디바이스 정보 - 빌드번호 연타 다다다다 하면 개발자옵션 메뉴가 열린다.

그럼 다시 설정 - 개발자옵션에서 무선 디버깅을 체크해준다.

난 단축키 설정 해놨지롱^^

 

2. Android studio 설정

Pair Devices Using Wi-FI를 클릭

 

3. 기기 페어링

QR코드가 생성되면 무선 디버깅에서 QR코드로 기기 페어링!

 

 

와우!

 

안드로이드 스튜디오를 최초에 설치할 때 sdk tool-kit 설정이 잘못되면 안되기도 하는데.. 그건 잘 설치해보시도록!

 

[Android] android studio - github 연동

 

여태 작업한 것들을 블로그에만 남겨두니 손해란 느낌이 들어서.. 개발 공부 처음에 했을 때 만든 github 계정을 연동하기로 했다. 그래서 앞으로 안드로이드 작업한 내용을 commit 할 예정이다.

 

1. github 계정 생성

https://github.com/signup?ref_cta=Sign+up&ref_loc=header+logged+out&ref_page=%2F&source=header-home 

 

GitHub: Where the world builds software

GitHub is where over 73 million developers shape the future of software, together. Contribute to the open source community, manage your Git repositories, review code like a pro, track bugs and feat...

github.com

오우 galaxy...!

 

창피함에 눈물난다.

 

2. Git 버전 확인

Ctrl + Alt + s or File - Settings에서 Version Control - Git

최상단 우측에 Test 버튼을 클릭하면 하단에 Git version 이 나온다. 안드 버전이 옛날 거라 2.28.0으로 나오넴..

 

3. GitHub 계정 추가

그 바로 밑에 GitHub에 들어가 Add Account.. 를 누르면

 

뭔데

Use Token을 이용해 토큰으로 접속해보자.

GitHub - Settings - Developer settings - Personal access tokens 

repo, admin, gist 항목을 체크해주고 발급!

로그인성공

 

 

3. Branch 변경

다시 Settings로 들어가서 보면

 

아마 default로 branch 설정이 master로 되어있을 텐데... GitHub에서 repository를 만들면 main이라는 branch가 default로 지정된다. 그 이유는 'Black Lives Matter' 운동에 발맞춰서 master, slave 이런 용어 쓰지 말자나 뭐라나..

 

 

4. GitHub로 commit

VCS - import into Version Control - Share Project on GitHub

 

 

빨간색으로 된 것들은 전부 commit 대상

 

 

GitHub 안에서 먼저 repository를 만들어도 된다. 없어서 이렇게 뜨는 것.

이렇게 샘플 repository를 생성한 후 https 주소를 복사한다.

프로젝트에 Git Bash Here로 열어서

 

git remote add origin [git 주소]

 

바아로 Ctrl + k 눌러서 커밋 때려버린다.

 

 

 

 

<참고>

https://velog.io/@modolee/github-renaming-the-default-branch-from-master-to-main

 

Github 기본 생성 브랜치 이름을 'master' 에서 'main' 으로 변경

얼마 전 Github에 생긴 작지만 큰 변화에 대해서 알아봅니다.

velog.io

https://velog.io/@kiyoog02/Android-Studio%EC%99%80-GitHub-%EC%97%B0%EB%8F%99

 

[Android / Java] firebase를 이용한 구글 로그인 연동

 

지난 시간 firebase와 연동을 했다면 어렵지 않게 작업할 수 있을 것이다.

https://minggu92.tistory.com/75

 

[Android] firebase 연동 (최신버전)

[Android] firebase 연동 오랜만에 돌아온 안드로이드 시간. 원래는 블로그를 내가 자주 잊을 수 있는 안드로이드 위주로만 올리려고 하다가.. 웹 개발 쪽 이것저것 올리다 보니 소홀해졌었다. 따라서

minggu92.tistory.com

 

저번에 firebase-sample-java로 만들어서 먼저 java로 작업을 해보도록 하겠다.

 

1. firebase console에 구글 인증 추가

Authentication 클릭

구글 클릭하고 내 메일 넣으면

짜잔.

등록을 안 하면 ApiException 12500 에러가 뜬다!

 

 

2. 구글 인증을 위한 SDK 추가

App수준 Gradle 추가

plugins {
    id 'com.android.application'
    id 'com.google.gms.google-services' //구글서비스 추가
}

android {
    compileSdk 32

    defaultConfig {
        applicationId "com.example.firebase_sample"
        minSdk 21
        targetSdk 32
        versionCode 1
        versionName "1.0"

        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
}

dependencies {

    implementation 'androidx.appcompat:appcompat:1.4.1'
    implementation 'com.google.android.material:material:1.5.0'
    implementation 'androidx.constraintlayout:constraintlayout:2.1.3'
    testImplementation 'junit:junit:4.13.2'
    androidTestImplementation 'androidx.test.ext:junit:1.1.3'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'

    // Import the Firebase BoM
    implementation platform('com.google.firebase:firebase-bom:29.2.0')

    // Add the dependency for the Firebase SDK for Google Analytics
    // When using the BoM, don't specify versions in Firebase dependencies
    implementation 'com.google.firebase:firebase-analytics'

    // Add the dependencies for any other desired Firebase products
    // https://firebase.google.com/docs/android/setup#available-libraries

    // Declare the dependency for the Firebase Authentication library
    // When using the BoM, you don't specify versions in Firebase library dependencies
    implementation 'com.google.firebase:firebase-auth'

    // Also declare the dependency for the Google Play services library and specify its version
    implementation 'com.google.android.gms:play-services-auth:20.1.0'

}

 

 

3. layout 수정

가볍게 구글 로그인 버튼과 로그아웃 버튼을 만들어보자.

activity_main.xml에 버튼을 추가해준다.

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <com.google.android.gms.common.SignInButton
        android:id="@+id/btn_google_sign_in"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <Button
        android:id="@+id/btn_logout_google"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="구글 로그아웃"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintHorizontal_bias="0.498"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_bias="0.587" />


</androidx.constraintlayout.widget.ConstraintLayout>

미스터 심플심플 어쩌구저쩌구

 

4. strings.xml 추가

<resources>
    <string name="app_name">firebase-sample</string>
    <string name="success_login">구글 로그인 성공</string>
    <string name="failed_login">구글 로그인 실패</string>
    <string name="success_logout">로그아웃 되었습니다</string>
    <string name="status_login">이미 로그인 되어있습니다</string>
</resources>

미리 토스트 메시지요 문자열을 추가해두자.

 

5. MainActivity 수정

5.1) GoogleSignInOptions 객체 선언

근데 저 default_web_client_id는 아래 google cloud platform에서 확인할 수 있는데 

내가 설정 안 해도 자동으로 firebase가 만들어주더라..... 

 

 @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // 파이어베이스 인증 객체 선언
        mAuth = FirebaseAuth.getInstance();

        // Google 로그인을 앱에 통합
        // GoogleSignInOptions 개체를 구성할 때 requestIdToken을 호출
        GoogleSignInOptions googleSignInOptions = new GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
                .requestIdToken(getString(R.string.default_web_client_id))
                .requestEmail()
                .build();

        mGoogleSignInClient = GoogleSignIn.getClient(this, googleSignInOptions);

        btnGoogleLogin = findViewById(R.id.btn_google_sign_in);
        btnGoogleLogin.setOnClickListener(view -> {
            // 기존에 로그인 했던 계정을 확인한다.
            gsa = GoogleSignIn.getLastSignedInAccount(MainActivity.this);

            if (gsa != null) // 로그인 되있는 경우
                Toast.makeText(MainActivity.this, R.string.status_login, Toast.LENGTH_SHORT).show();
            else
                signIn();
        });

        btnLogoutGoogle = findViewById(R.id.btn_logout_google);
        btnLogoutGoogle.setOnClickListener(view -> {
            signOut(); //로그아웃
        });
    }

공식문서는 간단하게 만들었지만 우리는 버튼도 만들어뒀으니 클릭이벤트도 달아주자.

기존에 로그인했더라면 이미 로그인되어있다는 토스트 메시지를 띄워줄 것이다.

 

5.2) 로그인 처리(onActivityResult)

로그인 처리가 정상적으로 이루어지면 GoogleSignInAccount 객체를 받아온다. 

    
    private void signIn(){
        Intent signInIntent = mGoogleSignInClient.getSignInIntent();
        startActivityForResult(signInIntent, RC_SIGN_IN);
    }

    @Override
    public void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);

        // Result returned from launching the Intent from GoogleSignInClient.getSignInIntent(...);
        if (requestCode == RC_SIGN_IN) {
            // The Task returned from this call is always completed, no need to attach a listener.
            Task<GoogleSignInAccount> task = GoogleSignIn.getSignedInAccountFromIntent(data);
            handleSignInResult(task);
        }
    }

Intent와 함께 넘긴 RC_SIGN_IN 상수를 제대로 돌려받았다면 Task 객체를 가져와 handleSignInResult 함수에 태워주자.

 

5.3) 로그인 후처리 (계정 정보 가져오기) 

    /* 사용자 정보 가져오기 */
    private void handleSignInResult(Task<GoogleSignInAccount> completedTask) {
        try {
            GoogleSignInAccount acct = completedTask.getResult(ApiException.class);

            if (acct != null) {
                firebaseAuthWithGoogle(acct.getIdToken());

                String personName = acct.getDisplayName();
                String personGivenName = acct.getGivenName();
                String personFamilyName = acct.getFamilyName();
                String personEmail = acct.getEmail();
                String personId = acct.getId();
                Uri personPhoto = acct.getPhotoUrl();

                Log.d(TAG, "handleSignInResult:personName "+personName);
                Log.d(TAG, "handleSignInResult:personGivenName "+personGivenName);
                Log.d(TAG, "handleSignInResult:personEmail "+personEmail);
                Log.d(TAG, "handleSignInResult:personId "+personId);
                Log.d(TAG, "handleSignInResult:personFamilyName "+personFamilyName);
                Log.d(TAG, "handleSignInResult:personPhoto "+personPhoto);
            }
        } catch (ApiException e) {
            // The ApiException status code indicates the detailed failure reason.
            // Please refer to the GoogleSignInStatusCodes class reference for more information.
            Log.e(TAG, "signInResult:failed code=" + e.getStatusCode());
        }
    }

    
    // [START auth_with_google]
    private void firebaseAuthWithGoogle(String idToken) {
        AuthCredential credential = GoogleAuthProvider.getCredential(idToken, null);
        mAuth.signInWithCredential(credential)
                .addOnCompleteListener(this, task -> {
                    if (task.isSuccessful()) {
                        // Sign in success, update UI with the signed-in user's information
                        Log.d(TAG, "signInWithCredential:success");
                        Toast.makeText(MainActivity.this, R.string.success_login, Toast.LENGTH_SHORT).show();
                        FirebaseUser user = mAuth.getCurrentUser();
//                            updateUI(user);
                    } else {
                        // If sign in fails, display a message to the user.
                        Log.w(TAG, "signInWithCredential:failure", task.getException());
                        Toast.makeText(MainActivity.this, R.string.failed_login, Toast.LENGTH_SHORT).show();
//                            updateUI(null);
                    }
                });
    }

성공적으로 로그인이 되었을 때 FirebaseAuth 객체의 공유 인스턴스를 가져오고 GoogleSignInAccount 객체에서 ID 토큰을 가져와서 Firebase 사용자 인증 정보로 교환한다.

ui 업데이트하는 메서드는 지금 따로 ui가 없으니까 뭐 주석처리를 하고

 

5.4) 로그아웃 및 계정 삭제처리

    /* 로그아웃 */
    private void signOut() {
        mGoogleSignInClient.signOut()
                .addOnCompleteListener(this, task -> {
                    mAuth.signOut();
                    Toast.makeText(MainActivity.this, R.string.success_logout, Toast.LENGTH_SHORT).show();
                    // ...
                });
        gsa = null;
    }

    /* 회원 삭제요청 */
    private void revokeAccess() {
        mGoogleSignInClient.revokeAccess()
                .addOnCompleteListener(this, task -> {
                    // ...
                });
    }

공식문서는 FirebaseAuth인스턴스만 로그아웃하지만 GoogleSignInClient도 같이 로그아웃해주도록 하자.

 

 

 

5. 최종본 및 테스트

package com.example.firebase_sample;

import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.util.Log;
import android.widget.Button;
import android.widget.Toast;

import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;

import com.google.android.gms.auth.api.signin.GoogleSignIn;
import com.google.android.gms.auth.api.signin.GoogleSignInAccount;
import com.google.android.gms.auth.api.signin.GoogleSignInClient;
import com.google.android.gms.auth.api.signin.GoogleSignInOptions;
import com.google.android.gms.common.SignInButton;
import com.google.android.gms.common.api.ApiException;
import com.google.android.gms.tasks.OnCompleteListener;
import com.google.android.gms.tasks.Task;
import com.google.firebase.auth.AuthCredential;
import com.google.firebase.auth.FirebaseAuth;
import com.google.firebase.auth.FirebaseUser;
import com.google.firebase.auth.GoogleAuthProvider;

public class MainActivity extends AppCompatActivity {
    private static final String TAG = "MainActivity";
    private static final int RC_SIGN_IN = 9001;

    // 구글api클라이언트
    private GoogleSignInClient mGoogleSignInClient;

    // 구글 계정
    private GoogleSignInAccount gsa;

    // 파이어베이스 인증 객체 생성
    private FirebaseAuth mAuth;

    // 구글  로그인 버튼
    private SignInButton btnGoogleLogin;
    private Button btnLogoutGoogle;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // 파이어베이스 인증 객체 선언
        mAuth = FirebaseAuth.getInstance();

        // Google 로그인을 앱에 통합
        // GoogleSignInOptions 개체를 구성할 때 requestIdToken을 호출
        GoogleSignInOptions googleSignInOptions = new GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
                .requestIdToken(getString(R.string.default_web_client_id))
                .requestEmail()
                .build();

        mGoogleSignInClient = GoogleSignIn.getClient(this, googleSignInOptions);

        btnGoogleLogin = findViewById(R.id.btn_google_sign_in);
        btnGoogleLogin.setOnClickListener(view -> {
            // 기존에 로그인 했던 계정을 확인한다.
            gsa = GoogleSignIn.getLastSignedInAccount(MainActivity.this);

            if (gsa != null) // 로그인 되있는 경우
                Toast.makeText(MainActivity.this, R.string.status_login, Toast.LENGTH_SHORT).show();
            else
                signIn();
        });

        btnLogoutGoogle = findViewById(R.id.btn_logout_google);
        btnLogoutGoogle.setOnClickListener(view -> {
            signOut(); //로그아웃
        });
    }

    private void signIn(){
        Intent signInIntent = mGoogleSignInClient.getSignInIntent();
        startActivityForResult(signInIntent, RC_SIGN_IN);
    }

    @Override
    public void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);

        // Result returned from launching the Intent from GoogleSignInClient.getSignInIntent(...);
        if (requestCode == RC_SIGN_IN) {
            // The Task returned from this call is always completed, no need to attach a listener.
            Task<GoogleSignInAccount> task = GoogleSignIn.getSignedInAccountFromIntent(data);
            handleSignInResult(task);
        }
    }


    /* 사용자 정보 가져오기 */
    private void handleSignInResult(Task<GoogleSignInAccount> completedTask) {
        try {
            GoogleSignInAccount acct = completedTask.getResult(ApiException.class);

            if (acct != null) {
                firebaseAuthWithGoogle(acct.getIdToken());

                String personName = acct.getDisplayName();
                String personGivenName = acct.getGivenName();
                String personFamilyName = acct.getFamilyName();
                String personEmail = acct.getEmail();
                String personId = acct.getId();
                Uri personPhoto = acct.getPhotoUrl();

                Log.d(TAG, "handleSignInResult:personName "+personName);
                Log.d(TAG, "handleSignInResult:personGivenName "+personGivenName);
                Log.d(TAG, "handleSignInResult:personEmail "+personEmail);
                Log.d(TAG, "handleSignInResult:personId "+personId);
                Log.d(TAG, "handleSignInResult:personFamilyName "+personFamilyName);
                Log.d(TAG, "handleSignInResult:personPhoto "+personPhoto);
            }
        } catch (ApiException e) {
            // The ApiException status code indicates the detailed failure reason.
            // Please refer to the GoogleSignInStatusCodes class reference for more information.
            Log.e(TAG, "signInResult:failed code=" + e.getStatusCode());
        }
    }


    // [START auth_with_google]
    private void firebaseAuthWithGoogle(String idToken) {
        AuthCredential credential = GoogleAuthProvider.getCredential(idToken, null);
        mAuth.signInWithCredential(credential)
                .addOnCompleteListener(this, task -> {
                    if (task.isSuccessful()) {
                        // Sign in success, update UI with the signed-in user's information
                        Log.d(TAG, "signInWithCredential:success");
                        Toast.makeText(MainActivity.this, R.string.success_login, Toast.LENGTH_SHORT).show();
                        FirebaseUser user = mAuth.getCurrentUser();
//                            updateUI(user);
                    } else {
                        // If sign in fails, display a message to the user.
                        Log.w(TAG, "signInWithCredential:failure", task.getException());
                        Toast.makeText(MainActivity.this, R.string.failed_login, Toast.LENGTH_SHORT).show();
//                            updateUI(null);
                    }
                });
    }

    private void updateUI(FirebaseUser user) {

    }

    /* 로그아웃 */
    private void signOut() {
        mGoogleSignInClient.signOut()
                .addOnCompleteListener(this, task -> {
                    mAuth.signOut();
                    Toast.makeText(MainActivity.this, R.string.success_logout, Toast.LENGTH_SHORT).show();
                    // ...
                });
        gsa = null;
    }

    /* 회원 삭제요청 */
    private void revokeAccess() {
        mGoogleSignInClient.revokeAccess()
                .addOnCompleteListener(this, task -> {
                    // ...
                });
    }
}

 

 

 

디버그 로그

/MainActivity: handleSignInResult:personName 밍구
D/MainActivity: handleSignInResult:personGivenName 밍구
D/MainActivity: handleSignInResult:personEmail --@gmail.com
D/MainActivity: handleSignInResult:personId --
D/MainActivity: handleSignInResult:personFamilyName --
D/MainActivity: handleSignInResult:personPhoto --

 

 

 

전체 소스는 github에 올려두었다.

https://github.com/minha9012/firebase-sample

 

GitHub - minha9012/firebase-sample: firebase & google login sample

firebase & google login sample. Contribute to minha9012/firebase-sample development by creating an account on GitHub.

github.com

 

 

<참고>

https://firebase.google.com/docs/auth/android/google-signin

 

Android에서 Google 로그인을 사용하여 인증  |  Firebase Documentation

Join Firebase at Google I/O 2022 live from Shoreline Amphitheatre and online May 11-12. Register now 의견 보내기 Android에서 Google 로그인을 사용하여 인증 Google 로그인을 앱에 통합하여 사용자가 Google 계정으로 Firebase에

firebase.google.com

 

[Android] firebase 연동

 

오랜만에 돌아온 안드로이드 시간.

원래는 블로그를 내가 자주 잊을 수 있는 안드로이드 위주로만 올리려고 하다가.. 웹 개발 쪽 이것저것 올리다 보니 소홀해졌었다.

따라서 감도 살리고 최신 기술의 동향도 살필 겸 스터디 모임을 통해 안드로이드를 다시 잡게 되었다.

그 첫번째는 firebase를 연동하는 것이다.

공식문서와 함께 작업해보자.

우리는 옵션1로 시작할 것이다.

 

1. 새 android project 생성

먼저 프로젝트를 생성해놔야 코드를 넣을 수 있다.

empty project를 하나 만들어주자.

롱타임노씨 브로..

 

2. firebase 회원가입 및 프로젝트 생성

회원가입은 편하게 구글로 하면 된다.

짓고 싶은 이름으로 하나 만들어준다.

google analytics 설정을 안 하면 바로 빌드된다.

 

3. Android 앱에 Firebase 추가

홈 화면에서 '앱에 Firebase를 추가하여 시작하기 - Android' 로고를 선택

 

3.1) package 명 입력

최상단 패키지명을 입력하면 된다.

 

3.2) SHA-1 입력

Android Studio 우측에 Gradle 버튼을 클릭한 후 gradle signingReport라고 입력하면

위와 같이 SHA-1 번호가 나온다.

입력 후 앱을 등록해보자.

 

3.3) google-services.json 파일을 App폴더에 넣어주기

해당 json 파일을 다운받아 프로젝트 내 App폴더에 드래그 앤 드롭으로 넣어주면 된다.

Project 단위로 보면 잘 들어갔음을 확인할 수 있다.

 

 

3.4) firebase sdk 추가

문서 업데이트 좀 해라 ㅜ

공식문서에 나온 gradle 설정이 이 방식이 아니다.. 아래와 같이 해야한다.

프로젝트 단위 gradle과 모듈단위 gradle을 각각 열어준다.

 

3.4.1) 모듈 수준 Gradle 설정

// Top-level build file where you can add configuration options common to all sub-projects/modules.
plugins {
    id 'com.android.application' version '7.1.2' apply false
    id 'com.android.library' version '7.1.2' apply false
    //구글 서비스 추가
    id 'com.google.gms.google-services' version '4.3.10' apply false
}

task clean(type: Delete) {
    delete rootProject.buildDir
}

 

3.4.2) 프로젝트 수준 Gradle 설정

plugins {
    id 'com.android.application'
    id 'com.google.gms.google-services' //구글서비스 추가
}

android {
    compileSdk 32

    defaultConfig {
        applicationId "com.example.firebase_sample"
        minSdk 21
        targetSdk 32
        versionCode 1
        versionName "1.0"

        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
}

dependencies {

    implementation 'androidx.appcompat:appcompat:1.4.1'
    implementation 'com.google.android.material:material:1.5.0'
    implementation 'androidx.constraintlayout:constraintlayout:2.1.3'
    testImplementation 'junit:junit:4.13.2'
    androidTestImplementation 'androidx.test.ext:junit:1.1.3'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'

    // Import the Firebase BoM
    implementation platform('com.google.firebase:firebase-bom:29.2.0')

    // Add the dependency for the Firebase SDK for Google Analytics
    // When using the BoM, don't specify versions in Firebase dependencies
    implementation 'com.google.firebase:firebase-analytics'

    // Add the dependencies for any other desired Firebase products
    // https://firebase.google.com/docs/android/setup#available-libraries

    // Declare the dependency for the Firebase Authentication library
    // When using the BoM, you don't specify versions in Firebase library dependencies
    implementation 'com.google.firebase:firebase-auth'

}

 

이제 firebase 콘솔 쪽을 들여다보자.

 

해당 앱이 추가되었음을 확인할 수 있다.

다음 시간엔 Authentication을 적용해보도록 하자.

 

 

<참고>

https://firebase.google.com/docs/android/setup

 

Android 프로젝트에 Firebase 추가  |  Firebase Documentation

Join Firebase at Google I/O 2022 live from Shoreline Amphitheatre and online May 11-12. Register now 의견 보내기 Android 프로젝트에 Firebase 추가 기본 요건 Android 프로젝트가 준비되지 않았다면 빠른 시작 샘플 중 하나를

firebase.google.com

 

 

솔직히 도커는 고래이미지가 더 귀엽지 않나...쩝

[Docker] Failed to get D-Bus connection: Operation not permitted

 

dockerfile을 실행해 container를 run 하고 서비스를 실행하려고 하면 에러가 난다.

#dockerfile 실행
[minggu92@cloud dockerfiles]$ docker run -it dockerfile_test:1.0 /bin/bash

#유저변경
[root@a81f33368378 /]# su - minggu1

#sshd 서비스 상태 확인
[minggu1@a81f33368378 ~]$ systemctl status sshd
Failed to get D-Bus connection: Operation not permitted
[minggu1@a81f33368378 ~]$ sudo systemctl status sshd
Failed to get D-Bus connection: Operation not permitted

 

1. docker 명령어 해결

docker container는 기본적으로 Unprivileged모드로 실행되며 이상태에서는 시스템 주요자원에 접근할 수 있는 권한이 부족하기 때문에 --privileged옵션을 주어야한다.

-d옵션을 사용하면 컨테이너가 detached 모드에서 실행되며 이 모드가 없을 때 Ctrl + C로 빠져나오면 컨테이너는 종료된다.

systemctl 명령어를 사용하려면 /sbin/init을 사용한다.

대신 해당 명령어로 실행시키면 attach 대신 docker exec -it [service] bash로 접근해야 한다.

#docker 실행
[minggu92@cloud dockerfiles]$ docker run --privileged -p 1002:22 -d --name minggu1_ssh dockerfile_test:1.0  /sbin/init
d45e8fc8ba2c821eccea71d0ca00316f7f9fa621e8d4f10b433cab8def6b413c
[minggu92@skylark_dev dockerfiles]# docker exec -it minggu1_ssh /bin/bash

[root@d45e8fc8ba2c /]# su - minggu1
[minggu1@d45e8fc8ba2c ~]$ systemctl status sshd
● sshd.service - OpenSSH server daemon
   Loaded: loaded (/usr/lib/systemd/system/sshd.service; enabled; vendor preset: enabled)
   Active: active (running) since Tue 2022-03-22 04:16:54 UTC; 13s ago
     Docs: man:sshd(8)
           man:sshd_config(5)
 Main PID: 92 (sshd)
   CGroup: /docker/d45e8fc8ba2c821eccea71d0ca00316f7f9fa621e8d4f10b433cab8def6b413c/system.slice/sshd.service
           └─92 /usr/sbin/sshd -D
           ‣ 92 /usr/sbin/sshd -D
[minggu1@d45e8fc8ba2c ~]$

 

2. Dockerfile에 ENTRYPOINT 추가

# 1. pull OS image (centos or ubuntu)
# format : FROM [image]:[tag]
FROM centos:7
# FROM ubuntu:18.04

# 2. 메타데이터 표시
LABEL "purpose"="ssh_test"
LABEL "author"="minggu"
ENV USER minggu1

# 3. 업데이트 및 네트워크 환경설정
#RUN ["[command]", "[parameter1]", "[parameter2]" ...]
# -y 명령어를 빼먹으면 도커 실행이 안된다. 반드시 yes 하자.
RUN yum -y update && yum -y install ntsysv initscripts net-tools sudo openssh-server openssh-clients openssh-askpass
RUN mkdir /var/run/sshd

# 4. minggu1유저에게 sudo 권한 생성
RUN sed -ri '20a'$USER'    ALL=(ALL) NOPASSWD:ALL' /etc/sudoers

# 5. .ssh 생성 및 권한주기
RUN useradd -m $USER
RUN mkdir /home/$USER/.ssh
RUN chown $USER.$USER /home/$USER/.ssh
RUN chmod 700 /home/$USER/.ssh

#6. 패스워드 설정
RUN echo 'root:root' | chpasswd
RUN echo $USER':test' | chpasswd

# 7. Generate Keys & 포트 22번 노출 지정 
RUN ssh-keygen -q -N "" -t rsa -f /etc/ssh/ssh_host_rsa_key
RUN ssh-keygen -q -N "" -t rsa -f /root/.ssh/id_rsa
RUN cp /root/.ssh/id_rsa.pub /root/.ssh/authorized_keys
EXPOSE 22

#ENTRYPOINT로 미리 명령어 실행할 수 있지만 도커 실행할 때 설정하는걸로
ENTRYPOINT["/sbin/init", "systemctl start sshd", "systemctl enable sshd"]

# 8. CMD : 컨테이너 생성시 시작명령어
#CMD ["[command]", "[parameter1]", "[parameter2]" ...]
#CMD ["[parameter1]", "[parameter2" ...]
#CMD <전체커맨드>
CMD ["/usr/sbin/sshd", "-D"]

 

 

 

<참고>

https://blog.naver.com/alice_k106/220395077738

 

22. [Docker] centos:7 또는 centos:latest 이미지의 에러 : systemctl 작동 문제

우선 이 포스팅은 다음 포스트를 참조하여 작성하였음을 밝힌다. http://qiita.com/yunano/items/9637ee21a...

blog.naver.com

 

 

+ Recent posts